Commit 00299ccc37e71f5a8f31b510c1f5b617aa9dfca0

Authored by Victor Costa
1 parent f9ed0cce

rails3: fix integration tests

app/helpers/application_helper.rb
... ... @@ -678,7 +678,7 @@ module ApplicationHelper
678 678 lightbox_link_to '<span class="icon-menu-search"></span>'+ _('Search'), {
679 679 :controller => 'search',
680 680 :action => 'popup',
681   - :category_path => (@category ? @category.explode_path : []) },
  681 + :category_path => (@category ? @category.explode_path : nil)},
682 682 :id => 'open_search'
683 683 end
684 684 end
... ... @@ -1073,7 +1073,7 @@ module ApplicationHelper
1073 1073 links.push(_('New content') => colorbox_options({:href => url_for({:controller => 'cms', :action => 'new', :profile => current_user.login, :cms => true})}))
1074 1074 end
1075 1075  
1076   - link_to(content_tag(:span, _('Contents'), :class => 'icon-menu-articles'), {:controller => "search", :action => 'contents', :category_path => ''}, :id => 'submenu-contents') +
  1076 + link_to(content_tag(:span, _('Contents'), :class => 'icon-menu-articles'), {:controller => "search", :action => 'contents', :category_path => nil}, :id => 'submenu-contents') +
1077 1077 link_to(content_tag(:span, _('Contents menu')), '#', :onclick => "toggleSubmenu(this,'',#{j links.to_json}); return false", :class => 'menu-submenu-trigger up', :id => 'submenu-contents-trigger')
1078 1078 end
1079 1079 alias :browse_contents_menu :search_contents_menu
... ...
app/models/category.rb
1 1 class Category < ActiveRecord::Base
2 2  
3   - attr_accessible :name, :parent_id, :display_color, :display_in_menu, :image_builder, :environment
  3 + attr_accessible :name, :parent_id, :display_color, :display_in_menu, :image_builder, :environment, :parent
4 4  
5 5 SEARCHABLE_FIELDS = {
6 6 :name => 10,
... ...
app/views/layouts/_javascript.html.erb
... ... @@ -3,7 +3,7 @@
3 3 'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate',
4 4 'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput',
5 5 'add-and-join', 'report-abuse', 'catalog', 'manage-products',
6   -'jquery-ui-timepicker-addon', 'application.js', :cache => 'cache-general' %>
  6 +'jquery-ui-timepicker-addon', 'application.js', 'rails.js', :cache => 'cache-general' %>
7 7  
8 8 <% language = FastGettext.locale %>
9 9 <% %w{messages methods}.each do |type| %>
... ...
config/routes.rb
... ... @@ -125,7 +125,7 @@ Noosfero::Application.routes.draw do
125 125 # cache stuff - hack
126 126 match 'public/:action/:id', :controller => 'public'
127 127  
128   - match ':profile/*page/versions', :controller => 'content_viewer', :action => 'article_versions', :profile => /#{Noosfero.identifier_format}/, :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } }
  128 + match ':profile/*page/versions', :controller => 'content_viewer', :action => 'article_versions', :profile => /#{Noosfero.identifier_format}/, :constraints => EnvironmentDomainConstraint.new
129 129 match '*page/versions', :controller => 'content_viewer', :action => 'article_versions'
130 130  
131 131 # match requests for profiles that don't have a custom domain
... ...
public/javascripts/rails.js 0 → 100644
... ... @@ -0,0 +1,398 @@
  1 +(function($, undefined) {
  2 +
  3 +/**
  4 + * Unobtrusive scripting adapter for jQuery
  5 + * https://github.com/rails/jquery-ujs
  6 + *
  7 + * Requires jQuery 1.7.0 or later.
  8 + *
  9 + * Released under the MIT license
  10 + *
  11 + */
  12 +
  13 + // Cut down on the number of issues from people inadvertently including jquery_ujs twice
  14 + // by detecting and raising an error when it happens.
  15 + if ( $.rails !== undefined ) {
  16 + $.error('jquery-ujs has already been loaded!');
  17 + }
  18 +
  19 + // Shorthand to make it a little easier to call public rails functions from within rails.js
  20 + var rails;
  21 + var $document = $(document);
  22 +
  23 + $.rails = rails = {
  24 + // Link elements bound by jquery-ujs
  25 + linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]',
  26 +
  27 + // Button elements bound by jquery-ujs
  28 + buttonClickSelector: 'button[data-remote]',
  29 +
  30 + // Select elements bound by jquery-ujs
  31 + inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',
  32 +
  33 + // Form elements bound by jquery-ujs
  34 + formSubmitSelector: 'form',
  35 +
  36 + // Form input elements bound by jquery-ujs
  37 + formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])',
  38 +
  39 + // Form input elements disabled during form submission
  40 + disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]',
  41 +
  42 + // Form input elements re-enabled after form submission
  43 + enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled',
  44 +
  45 + // Form required input elements
  46 + requiredInputSelector: 'input[name][required]:not([disabled]),textarea[name][required]:not([disabled])',
  47 +
  48 + // Form file input elements
  49 + fileInputSelector: 'input[type=file]',
  50 +
  51 + // Link onClick disable selector with possible reenable after remote submission
  52 + linkDisableSelector: 'a[data-disable-with]',
  53 +
  54 + // Make sure that every Ajax request sends the CSRF token
  55 + CSRFProtection: function(xhr) {
  56 + var token = $('meta[name="csrf-token"]').attr('content');
  57 + if (token) xhr.setRequestHeader('X-CSRF-Token', token);
  58 + },
  59 +
  60 + // making sure that all forms have actual up-to-date token(cached forms contain old one)
  61 + refreshCSRFTokens: function(){
  62 + var csrfToken = $('meta[name=csrf-token]').attr('content');
  63 + var csrfParam = $('meta[name=csrf-param]').attr('content');
  64 + $('form input[name="' + csrfParam + '"]').val(csrfToken);
  65 + },
  66 +
  67 + // Triggers an event on an element and returns false if the event result is false
  68 + fire: function(obj, name, data) {
  69 + var event = $.Event(name);
  70 + obj.trigger(event, data);
  71 + return event.result !== false;
  72 + },
  73 +
  74 + // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
  75 + confirm: function(message) {
  76 + return confirm(message);
  77 + },
  78 +
  79 + // Default ajax function, may be overridden with custom function in $.rails.ajax
  80 + ajax: function(options) {
  81 + return $.ajax(options);
  82 + },
  83 +
  84 + // Default way to get an element's href. May be overridden at $.rails.href.
  85 + href: function(element) {
  86 + return element.attr('href');
  87 + },
  88 +
  89 + // Submits "remote" forms and links with ajax
  90 + handleRemote: function(element) {
  91 + var method, url, data, elCrossDomain, crossDomain, withCredentials, dataType, options;
  92 +
  93 + if (rails.fire(element, 'ajax:before')) {
  94 + elCrossDomain = element.data('cross-domain');
  95 + crossDomain = elCrossDomain === undefined ? null : elCrossDomain;
  96 + withCredentials = element.data('with-credentials') || null;
  97 + dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType);
  98 +
  99 + if (element.is('form')) {
  100 + method = element.attr('method');
  101 + url = element.attr('action');
  102 + data = element.serializeArray();
  103 + // memoized value from clicked submit button
  104 + var button = element.data('ujs:submit-button');
  105 + if (button) {
  106 + data.push(button);
  107 + element.data('ujs:submit-button', null);
  108 + }
  109 + } else if (element.is(rails.inputChangeSelector)) {
  110 + method = element.data('method');
  111 + url = element.data('url');
  112 + data = element.serialize();
  113 + if (element.data('params')) data = data + "&" + element.data('params');
  114 + } else if (element.is(rails.buttonClickSelector)) {
  115 + method = element.data('method') || 'get';
  116 + url = element.data('url');
  117 + data = element.serialize();
  118 + if (element.data('params')) data = data + "&" + element.data('params');
  119 + } else {
  120 + method = element.data('method');
  121 + url = rails.href(element);
  122 + data = element.data('params') || null;
  123 + }
  124 +
  125 + options = {
  126 + type: method || 'GET', data: data, dataType: dataType,
  127 + // stopping the "ajax:beforeSend" event will cancel the ajax request
  128 + beforeSend: function(xhr, settings) {
  129 + if (settings.dataType === undefined) {
  130 + xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
  131 + }
  132 + return rails.fire(element, 'ajax:beforeSend', [xhr, settings]);
  133 + },
  134 + success: function(data, status, xhr) {
  135 + element.trigger('ajax:success', [data, status, xhr]);
  136 + },
  137 + complete: function(xhr, status) {
  138 + element.trigger('ajax:complete', [xhr, status]);
  139 + },
  140 + error: function(xhr, status, error) {
  141 + element.trigger('ajax:error', [xhr, status, error]);
  142 + },
  143 + crossDomain: crossDomain
  144 + };
  145 +
  146 + // There is no withCredentials for IE6-8 when
  147 + // "Enable native XMLHTTP support" is disabled
  148 + if (withCredentials) {
  149 + options.xhrFields = {
  150 + withCredentials: withCredentials
  151 + };
  152 + }
  153 +
  154 + // Only pass url to `ajax` options if not blank
  155 + if (url) { options.url = url; }
  156 +
  157 + var jqxhr = rails.ajax(options);
  158 + element.trigger('ajax:send', jqxhr);
  159 + return jqxhr;
  160 + } else {
  161 + return false;
  162 + }
  163 + },
  164 +
  165 + // Handles "data-method" on links such as:
  166 + // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
  167 + handleMethod: function(link) {
  168 + var href = rails.href(link),
  169 + method = link.data('method'),
  170 + target = link.attr('target'),
  171 + csrfToken = $('meta[name=csrf-token]').attr('content'),
  172 + csrfParam = $('meta[name=csrf-param]').attr('content'),
  173 + form = $('<form method="post" action="' + href + '"></form>'),
  174 + metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';
  175 +
  176 + if (csrfParam !== undefined && csrfToken !== undefined) {
  177 + metadataInput += '<input name="' + csrfParam + '" value="' + csrfToken + '" type="hidden" />';
  178 + }
  179 +
  180 + if (target) { form.attr('target', target); }
  181 +
  182 + form.hide().append(metadataInput).appendTo('body');
  183 + form.submit();
  184 + },
  185 +
  186 + /* Disables form elements:
  187 + - Caches element value in 'ujs:enable-with' data store
  188 + - Replaces element text with value of 'data-disable-with' attribute
  189 + - Sets disabled property to true
  190 + */
  191 + disableFormElements: function(form) {
  192 + form.find(rails.disableSelector).each(function() {
  193 + var element = $(this), method = element.is('button') ? 'html' : 'val';
  194 + element.data('ujs:enable-with', element[method]());
  195 + element[method](element.data('disable-with'));
  196 + element.prop('disabled', true);
  197 + });
  198 + },
  199 +
  200 + /* Re-enables disabled form elements:
  201 + - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
  202 + - Sets disabled property to false
  203 + */
  204 + enableFormElements: function(form) {
  205 + form.find(rails.enableSelector).each(function() {
  206 + var element = $(this), method = element.is('button') ? 'html' : 'val';
  207 + if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with'));
  208 + element.prop('disabled', false);
  209 + });
  210 + },
  211 +
  212 + /* For 'data-confirm' attribute:
  213 + - Fires `confirm` event
  214 + - Shows the confirmation dialog
  215 + - Fires the `confirm:complete` event
  216 +
  217 + Returns `true` if no function stops the chain and user chose yes; `false` otherwise.
  218 + Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
  219 + Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
  220 + return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
  221 + */
  222 + allowAction: function(element) {
  223 + var message = element.data('confirm'),
  224 + answer = false, callback;
  225 + if (!message) { return true; }
  226 +
  227 + if (rails.fire(element, 'confirm')) {
  228 + answer = rails.confirm(message);
  229 + callback = rails.fire(element, 'confirm:complete', [answer]);
  230 + }
  231 + return answer && callback;
  232 + },
  233 +
  234 + // Helper function which checks for blank inputs in a form that match the specified CSS selector
  235 + blankInputs: function(form, specifiedSelector, nonBlank) {
  236 + var inputs = $(), input, valueToCheck,
  237 + selector = specifiedSelector || 'input,textarea',
  238 + allInputs = form.find(selector);
  239 +
  240 + allInputs.each(function() {
  241 + input = $(this);
  242 + valueToCheck = input.is('input[type=checkbox],input[type=radio]') ? input.is(':checked') : input.val();
  243 + // If nonBlank and valueToCheck are both truthy, or nonBlank and valueToCheck are both falsey
  244 + if (!valueToCheck === !nonBlank) {
  245 +
  246 + // Don't count unchecked required radio if other radio with same name is checked
  247 + if (input.is('input[type=radio]') && allInputs.filter('input[type=radio]:checked[name="' + input.attr('name') + '"]').length) {
  248 + return true; // Skip to next input
  249 + }
  250 +
  251 + inputs = inputs.add(input);
  252 + }
  253 + });
  254 + return inputs.length ? inputs : false;
  255 + },
  256 +
  257 + // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
  258 + nonBlankInputs: function(form, specifiedSelector) {
  259 + return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
  260 + },
  261 +
  262 + // Helper function, needed to provide consistent behavior in IE
  263 + stopEverything: function(e) {
  264 + $(e.target).trigger('ujs:everythingStopped');
  265 + e.stopImmediatePropagation();
  266 + return false;
  267 + },
  268 +
  269 + // replace element's html with the 'data-disable-with' after storing original html
  270 + // and prevent clicking on it
  271 + disableElement: function(element) {
  272 + element.data('ujs:enable-with', element.html()); // store enabled state
  273 + element.html(element.data('disable-with')); // set to disabled state
  274 + element.bind('click.railsDisable', function(e) { // prevent further clicking
  275 + return rails.stopEverything(e);
  276 + });
  277 + },
  278 +
  279 + // restore element to its original state which was disabled by 'disableElement' above
  280 + enableElement: function(element) {
  281 + if (element.data('ujs:enable-with') !== undefined) {
  282 + element.html(element.data('ujs:enable-with')); // set to old enabled state
  283 + element.removeData('ujs:enable-with'); // clean up cache
  284 + }
  285 + element.unbind('click.railsDisable'); // enable element
  286 + }
  287 +
  288 + };
  289 +
  290 + if (rails.fire($document, 'rails:attachBindings')) {
  291 +
  292 + $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});
  293 +
  294 + $document.delegate(rails.linkDisableSelector, 'ajax:complete', function() {
  295 + rails.enableElement($(this));
  296 + });
  297 +
  298 + $document.delegate(rails.linkClickSelector, 'click.rails', function(e) {
  299 + var link = $(this), method = link.data('method'), data = link.data('params'), metaClick = e.metaKey || e.ctrlKey;
  300 + if (!rails.allowAction(link)) return rails.stopEverything(e);
  301 +
  302 + if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link);
  303 +
  304 + if (link.data('remote') !== undefined) {
  305 + if (metaClick && (!method || method === 'GET') && !data) { return true; }
  306 +
  307 + var handleRemote = rails.handleRemote(link);
  308 + // response from rails.handleRemote() will either be false or a deferred object promise.
  309 + if (handleRemote === false) {
  310 + rails.enableElement(link);
  311 + } else {
  312 + handleRemote.error( function() { rails.enableElement(link); } );
  313 + }
  314 + return false;
  315 +
  316 + } else if (link.data('method')) {
  317 + rails.handleMethod(link);
  318 + return false;
  319 + }
  320 + });
  321 +
  322 + $document.delegate(rails.buttonClickSelector, 'click.rails', function(e) {
  323 + var button = $(this);
  324 + if (!rails.allowAction(button)) return rails.stopEverything(e);
  325 +
  326 + rails.handleRemote(button);
  327 + return false;
  328 + });
  329 +
  330 + $document.delegate(rails.inputChangeSelector, 'change.rails', function(e) {
  331 + var link = $(this);
  332 + if (!rails.allowAction(link)) return rails.stopEverything(e);
  333 +
  334 + rails.handleRemote(link);
  335 + return false;
  336 + });
  337 +
  338 + $document.delegate(rails.formSubmitSelector, 'submit.rails', function(e) {
  339 + var form = $(this),
  340 + remote = form.data('remote') !== undefined,
  341 + blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),
  342 + nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
  343 +
  344 + if (!rails.allowAction(form)) return rails.stopEverything(e);
  345 +
  346 + // skip other logic when required values are missing or file upload is present
  347 + if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
  348 + return rails.stopEverything(e);
  349 + }
  350 +
  351 + if (remote) {
  352 + if (nonBlankFileInputs) {
  353 + // slight timeout so that the submit button gets properly serialized
  354 + // (make it easy for event handler to serialize form without disabled values)
  355 + setTimeout(function(){ rails.disableFormElements(form); }, 13);
  356 + var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
  357 +
  358 + // re-enable form elements if event bindings return false (canceling normal form submission)
  359 + if (!aborted) { setTimeout(function(){ rails.enableFormElements(form); }, 13); }
  360 +
  361 + return aborted;
  362 + }
  363 +
  364 + rails.handleRemote(form);
  365 + return false;
  366 +
  367 + } else {
  368 + // slight timeout so that the submit button gets properly serialized
  369 + setTimeout(function(){ rails.disableFormElements(form); }, 13);
  370 + }
  371 + });
  372 +
  373 + $document.delegate(rails.formInputClickSelector, 'click.rails', function(event) {
  374 + var button = $(this);
  375 +
  376 + if (!rails.allowAction(button)) return rails.stopEverything(event);
  377 +
  378 + // register the pressed submit button
  379 + var name = button.attr('name'),
  380 + data = name ? {name:name, value:button.val()} : null;
  381 +
  382 + button.closest('form').data('ujs:submit-button', data);
  383 + });
  384 +
  385 + $document.delegate(rails.formSubmitSelector, 'ajax:beforeSend.rails', function(event) {
  386 + if (this == event.target) rails.disableFormElements($(this));
  387 + });
  388 +
  389 + $document.delegate(rails.formSubmitSelector, 'ajax:complete.rails', function(event) {
  390 + if (this == event.target) rails.enableFormElements($(this));
  391 + });
  392 +
  393 + $(function(){
  394 + rails.refreshCSRFTokens();
  395 + });
  396 + }
  397 +
  398 +})( jQuery );
... ...
test/integration/assigning_validator_organizations_to_regions_test.rb
... ... @@ -10,8 +10,8 @@ class AssigningValidatorOrganizationsToRegionsTest &lt; ActionController::Integrati
10 10 org2 = Organization.create!(:name => 'Organization two', :identifier => 'org2')
11 11  
12 12 Region.destroy_all
13   - region1 = Region.create!(:name => "Region 1", :environment_id => env.id)
14   - region2 = Region.create!(:name => "Region 2", :environment_id => env.id)
  13 + region1 = create(Region, :name => "Region 1", :environment_id => env.id)
  14 + region2 = create(Region, :name => "Region 2", :environment_id => env.id)
15 15  
16 16 login('ze', 'test')
17 17  
... ...
test/integration/blocks_test.rb
... ... @@ -46,7 +46,7 @@ class BlocksTest &lt; ActionController::IntegrationTest
46 46 block = blog_on_article_block_bootstrap
47 47 p = block.owner
48 48 b = block.article
49   - f = fast_create(Folder, :name => 'Folder1', :profile_id => p.id)
  49 + f = Folder.create!(:name => 'Folder1', :profile => p)
50 50 b.parent = f
51 51 b.save!
52 52 get "/profile/#{block.owner.identifier}"
... ...
test/integration/categories_menu_test.rb
... ... @@ -32,8 +32,10 @@ class CategoriesMenuTest &lt; ActionController::IntegrationTest
32 32 end
33 33  
34 34 should 'cache the categories menu' do
35   - ActionView::Base.any_instance.expects(:cache).with(Environment.default.id.to_s + "_categories_menu")
  35 + ActionController::Base.perform_caching = true
  36 + HomeController.any_instance.expects(:fragment_cache_key).with(Environment.default.id.to_s + "_categories_menu").returns('dir')
36 37 get '/'
  38 + ActionController::Base.perform_caching = false
37 39 end
38 40  
39 41 end
... ...
test/integration/enable_disable_features_test.rb
... ... @@ -4,7 +4,6 @@ class EnableDisableFeaturesTest &lt; ActionController::IntegrationTest
4 4 all_fixtures
5 5  
6 6 def test_enable_features
7   - uses_host 'anhetegua.net'
8 7 login 'ze', 'test'
9 8  
10 9 get '/admin/features'
... ...
test/integration/manage_documents_test.rb
... ... @@ -83,7 +83,7 @@ class ManageDocumentsTest &lt; ActionController::IntegrationTest
83 83 get '/myprofile/myuser/cms'
84 84 assert_response :success
85 85  
86   - assert_tag :tag => 'a', :attributes => { :href => "/myprofile/myuser/cms/destroy/#{article.id}", :onclick => /confirm/ }
  86 + assert_tag :tag => 'a', :attributes => { :href => "/myprofile/myuser/cms/destroy/#{article.id}", 'data-confirm' => /Are you sure/ }
87 87 post "/myprofile/myuser/cms/destroy/#{article.id}"
88 88  
89 89 assert_response :redirect
... ...
test/integration/routing_test.rb
... ... @@ -258,16 +258,14 @@ class RoutingTest &lt; ActionController::IntegrationTest
258 258  
259 259 should 'have route to versions of an article' do
260 260  
261   - assert_routing('/ze/work/free-software/versions', :controller => 'content_viewer', :action => 'article_versions', :profile => 'ze', :page => ['work', "free-software"])
  261 + assert_routing('/ze/work/free-software/versions', :controller => 'content_viewer', :action => 'article_versions', :profile => 'ze', :page => 'work/free-software')
262 262 end
263 263  
264 264 should 'have route to versions of an article if profile has domain' do
265 265 user = create_user('testuser').person
266 266 domain = Domain.create!(:name => 'example.com', :owner => user)
267 267  
268   - ActionController::TestRequest.any_instance.expects(:host).returns('www.example.com')
269   -
270   - assert_routing('/work/free-software/versions', :controller => 'content_viewer', :action => 'article_versions', :page => [ 'work', 'free-software'] )
  268 + assert_routing('http://www.example.com/work/free-software/versions', :controller => 'content_viewer', :action => 'article_versions', :page => 'work/free-software')
271 269 end
272 270  
273 271  
... ...
test/integration/tiny_mce_languages_test.rb
... ... @@ -12,7 +12,7 @@ class TinyMceLanguagesTest &lt; ActionController::IntegrationTest
12 12  
13 13 def assert_exists_tinymce_language_file(file)
14 14 filename = Rails.root.join("public", "javascripts", "tinymce", "jscripts", "tiny_mce", file)
15   - assert(File.exists?(filename), filename + " must exist")
  15 + assert(File.exists?(filename), "#{filename} must exist")
16 16 end
17 17  
18 18  
... ...