Revision 1297:0a574315af3e .svn/pristine/74
| .svn/pristine/74/74843b67cd322d5f1fa051b32a2e14dd00a341ce.svn-base | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2012 Jean-Philippe Lang |
|
| 3 |
# |
|
| 4 |
# This program is free software; you can redistribute it and/or |
|
| 5 |
# modify it under the terms of the GNU General Public License |
|
| 6 |
# as published by the Free Software Foundation; either version 2 |
|
| 7 |
# of the License, or (at your option) any later version. |
|
| 8 |
# |
|
| 9 |
# This program is distributed in the hope that it will be useful, |
|
| 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 12 |
# GNU General Public License for more details. |
|
| 13 |
# |
|
| 14 |
# You should have received a copy of the GNU General Public License |
|
| 15 |
# along with this program; if not, write to the Free Software |
|
| 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 17 |
|
|
| 18 |
require File.expand_path('../../test_helper', __FILE__)
|
|
| 19 |
|
|
| 20 |
class RepositoriesGitControllerTest < ActionController::TestCase |
|
| 21 |
tests RepositoriesController |
|
| 22 |
|
|
| 23 |
fixtures :projects, :users, :roles, :members, :member_roles, |
|
| 24 |
:repositories, :enabled_modules |
|
| 25 |
|
|
| 26 |
REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s
|
|
| 27 |
REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin? |
|
| 28 |
PRJ_ID = 3 |
|
| 29 |
CHAR_1_HEX = "\xc3\x9c" |
|
| 30 |
NUM_REV = 28 |
|
| 31 |
|
|
| 32 |
## Git, Mercurial and CVS path encodings are binary. |
|
| 33 |
## Subversion supports URL encoding for path. |
|
| 34 |
## Redmine Mercurial adapter and extension use URL encoding. |
|
| 35 |
## Git accepts only binary path in command line parameter. |
|
| 36 |
## So, there is no way to use binary command line parameter in JRuby. |
|
| 37 |
JRUBY_SKIP = (RUBY_PLATFORM == 'java') |
|
| 38 |
JRUBY_SKIP_STR = "TODO: This test fails in JRuby" |
|
| 39 |
|
|
| 40 |
def setup |
|
| 41 |
@ruby19_non_utf8_pass = |
|
| 42 |
(RUBY_VERSION >= '1.9' && Encoding.default_external.to_s != 'UTF-8') |
|
| 43 |
|
|
| 44 |
User.current = nil |
|
| 45 |
@project = Project.find(PRJ_ID) |
|
| 46 |
@repository = Repository::Git.create( |
|
| 47 |
:project => @project, |
|
| 48 |
:url => REPOSITORY_PATH, |
|
| 49 |
:path_encoding => 'ISO-8859-1' |
|
| 50 |
) |
|
| 51 |
assert @repository |
|
| 52 |
@char_1 = CHAR_1_HEX.dup |
|
| 53 |
if @char_1.respond_to?(:force_encoding) |
|
| 54 |
@char_1.force_encoding('UTF-8')
|
|
| 55 |
end |
|
| 56 |
end |
|
| 57 |
|
|
| 58 |
def test_create_and_update |
|
| 59 |
@request.session[:user_id] = 1 |
|
| 60 |
assert_difference 'Repository.count' do |
|
| 61 |
post :create, :project_id => 'subproject1', |
|
| 62 |
:repository_scm => 'Git', |
|
| 63 |
:repository => {
|
|
| 64 |
:url => '/test', |
|
| 65 |
:is_default => '0', |
|
| 66 |
:identifier => 'test-create', |
|
| 67 |
:extra_report_last_commit => '1', |
|
| 68 |
} |
|
| 69 |
end |
|
| 70 |
assert_response 302 |
|
| 71 |
repository = Repository.first(:order => 'id DESC') |
|
| 72 |
assert_kind_of Repository::Git, repository |
|
| 73 |
assert_equal '/test', repository.url |
|
| 74 |
assert_equal true, repository.extra_report_last_commit |
|
| 75 |
|
|
| 76 |
put :update, :id => repository.id, |
|
| 77 |
:repository => {
|
|
| 78 |
:extra_report_last_commit => '0' |
|
| 79 |
} |
|
| 80 |
assert_response 302 |
|
| 81 |
repo2 = Repository.find(repository.id) |
|
| 82 |
assert_equal false, repo2.extra_report_last_commit |
|
| 83 |
end |
|
| 84 |
|
|
| 85 |
if File.directory?(REPOSITORY_PATH) |
|
| 86 |
## Ruby uses ANSI api to fork a process on Windows. |
|
| 87 |
## Japanese Shift_JIS and Traditional Chinese Big5 have 0x5c(backslash) problem |
|
| 88 |
## and these are incompatible with ASCII. |
|
| 89 |
## Git for Windows (msysGit) changed internal API from ANSI to Unicode in 1.7.10 |
|
| 90 |
## http://code.google.com/p/msysgit/issues/detail?id=80 |
|
| 91 |
## So, Latin-1 path tests fail on Japanese Windows |
|
| 92 |
WINDOWS_PASS = (Redmine::Platform.mswin? && |
|
| 93 |
Redmine::Scm::Adapters::GitAdapter.client_version_above?([1, 7, 10])) |
|
| 94 |
WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10" |
|
| 95 |
|
|
| 96 |
def test_get_new |
|
| 97 |
@request.session[:user_id] = 1 |
|
| 98 |
@project.repository.destroy |
|
| 99 |
get :new, :project_id => 'subproject1', :repository_scm => 'Git' |
|
| 100 |
assert_response :success |
|
| 101 |
assert_template 'new' |
|
| 102 |
assert_kind_of Repository::Git, assigns(:repository) |
|
| 103 |
assert assigns(:repository).new_record? |
|
| 104 |
end |
|
| 105 |
|
|
| 106 |
def test_browse_root |
|
| 107 |
assert_equal 0, @repository.changesets.count |
|
| 108 |
@repository.fetch_changesets |
|
| 109 |
@project.reload |
|
| 110 |
assert_equal NUM_REV, @repository.changesets.count |
|
| 111 |
|
|
| 112 |
get :show, :id => PRJ_ID |
|
| 113 |
assert_response :success |
|
| 114 |
assert_template 'show' |
|
| 115 |
assert_not_nil assigns(:entries) |
|
| 116 |
assert_equal 9, assigns(:entries).size |
|
| 117 |
assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
|
|
| 118 |
assert assigns(:entries).detect {|e| e.name == 'this_is_a_really_long_and_verbose_directory_name' && e.kind == 'dir'}
|
|
| 119 |
assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
|
|
| 120 |
assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
|
|
| 121 |
assert assigns(:entries).detect {|e| e.name == 'copied_README' && e.kind == 'file'}
|
|
| 122 |
assert assigns(:entries).detect {|e| e.name == 'new_file.txt' && e.kind == 'file'}
|
|
| 123 |
assert assigns(:entries).detect {|e| e.name == 'renamed_test.txt' && e.kind == 'file'}
|
|
| 124 |
assert assigns(:entries).detect {|e| e.name == 'filemane with spaces.txt' && e.kind == 'file'}
|
|
| 125 |
assert assigns(:entries).detect {|e| e.name == ' filename with a leading space.txt ' && e.kind == 'file'}
|
|
| 126 |
assert_not_nil assigns(:changesets) |
|
| 127 |
assert assigns(:changesets).size > 0 |
|
| 128 |
end |
|
| 129 |
|
|
| 130 |
def test_browse_branch |
|
| 131 |
assert_equal 0, @repository.changesets.count |
|
| 132 |
@repository.fetch_changesets |
|
| 133 |
@project.reload |
|
| 134 |
assert_equal NUM_REV, @repository.changesets.count |
|
| 135 |
get :show, :id => PRJ_ID, :rev => 'test_branch' |
|
| 136 |
assert_response :success |
|
| 137 |
assert_template 'show' |
|
| 138 |
assert_not_nil assigns(:entries) |
|
| 139 |
assert_equal 4, assigns(:entries).size |
|
| 140 |
assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
|
|
| 141 |
assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
|
|
| 142 |
assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
|
|
| 143 |
assert assigns(:entries).detect {|e| e.name == 'test.txt' && e.kind == 'file'}
|
|
| 144 |
assert_not_nil assigns(:changesets) |
|
| 145 |
assert assigns(:changesets).size > 0 |
|
| 146 |
end |
|
| 147 |
|
|
| 148 |
def test_browse_tag |
|
| 149 |
assert_equal 0, @repository.changesets.count |
|
| 150 |
@repository.fetch_changesets |
|
| 151 |
@project.reload |
|
| 152 |
assert_equal NUM_REV, @repository.changesets.count |
|
| 153 |
[ |
|
| 154 |
"tag00.lightweight", |
|
| 155 |
"tag01.annotated", |
|
| 156 |
].each do |t1| |
|
| 157 |
get :show, :id => PRJ_ID, :rev => t1 |
|
| 158 |
assert_response :success |
|
| 159 |
assert_template 'show' |
|
| 160 |
assert_not_nil assigns(:entries) |
|
| 161 |
assert assigns(:entries).size > 0 |
|
| 162 |
assert_not_nil assigns(:changesets) |
|
| 163 |
assert assigns(:changesets).size > 0 |
|
| 164 |
end |
|
| 165 |
end |
|
| 166 |
|
|
| 167 |
def test_browse_directory |
|
| 168 |
assert_equal 0, @repository.changesets.count |
|
| 169 |
@repository.fetch_changesets |
|
| 170 |
@project.reload |
|
| 171 |
assert_equal NUM_REV, @repository.changesets.count |
|
| 172 |
get :show, :id => PRJ_ID, :path => repository_path_hash(['images'])[:param] |
|
| 173 |
assert_response :success |
|
| 174 |
assert_template 'show' |
|
| 175 |
assert_not_nil assigns(:entries) |
|
| 176 |
assert_equal ['edit.png'], assigns(:entries).collect(&:name) |
|
| 177 |
entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
|
|
| 178 |
assert_not_nil entry |
|
| 179 |
assert_equal 'file', entry.kind |
|
| 180 |
assert_equal 'images/edit.png', entry.path |
|
| 181 |
assert_not_nil assigns(:changesets) |
|
| 182 |
assert assigns(:changesets).size > 0 |
|
| 183 |
end |
|
| 184 |
|
|
| 185 |
def test_browse_at_given_revision |
|
| 186 |
assert_equal 0, @repository.changesets.count |
|
| 187 |
@repository.fetch_changesets |
|
| 188 |
@project.reload |
|
| 189 |
assert_equal NUM_REV, @repository.changesets.count |
|
| 190 |
get :show, :id => PRJ_ID, :path => repository_path_hash(['images'])[:param], |
|
| 191 |
:rev => '7234cb2750b63f47bff735edc50a1c0a433c2518' |
|
| 192 |
assert_response :success |
|
| 193 |
assert_template 'show' |
|
| 194 |
assert_not_nil assigns(:entries) |
|
| 195 |
assert_equal ['delete.png'], assigns(:entries).collect(&:name) |
|
| 196 |
assert_not_nil assigns(:changesets) |
|
| 197 |
assert assigns(:changesets).size > 0 |
|
| 198 |
end |
|
| 199 |
|
|
| 200 |
def test_changes |
|
| 201 |
get :changes, :id => PRJ_ID, |
|
| 202 |
:path => repository_path_hash(['images', 'edit.png'])[:param] |
|
| 203 |
assert_response :success |
|
| 204 |
assert_template 'changes' |
|
| 205 |
assert_tag :tag => 'h2', :content => 'edit.png' |
|
| 206 |
end |
|
| 207 |
|
|
| 208 |
def test_entry_show |
|
| 209 |
get :entry, :id => PRJ_ID, |
|
| 210 |
:path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param] |
|
| 211 |
assert_response :success |
|
| 212 |
assert_template 'entry' |
|
| 213 |
# Line 19 |
|
| 214 |
assert_tag :tag => 'th', |
|
| 215 |
:content => '11', |
|
| 216 |
:attributes => { :class => 'line-num' },
|
|
| 217 |
:sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
|
|
| 218 |
end |
|
| 219 |
|
|
| 220 |
def test_entry_show_latin_1 |
|
| 221 |
if @ruby19_non_utf8_pass |
|
| 222 |
puts_ruby19_non_utf8_pass() |
|
| 223 |
elsif WINDOWS_PASS |
|
| 224 |
puts WINDOWS_SKIP_STR |
|
| 225 |
elsif JRUBY_SKIP |
|
| 226 |
puts JRUBY_SKIP_STR |
|
| 227 |
else |
|
| 228 |
with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do |
|
| 229 |
['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1| |
|
| 230 |
get :entry, :id => PRJ_ID, |
|
| 231 |
:path => repository_path_hash(['latin-1-dir', "test-#{@char_1}.txt"])[:param],
|
|
| 232 |
:rev => r1 |
|
| 233 |
assert_response :success |
|
| 234 |
assert_template 'entry' |
|
| 235 |
assert_tag :tag => 'th', |
|
| 236 |
:content => '1', |
|
| 237 |
:attributes => { :class => 'line-num' },
|
|
| 238 |
:sibling => { :tag => 'td',
|
|
| 239 |
:content => /test-#{@char_1}.txt/ }
|
|
| 240 |
end |
|
| 241 |
end |
|
| 242 |
end |
|
| 243 |
end |
|
| 244 |
|
|
| 245 |
def test_entry_download |
|
| 246 |
get :entry, :id => PRJ_ID, |
|
| 247 |
:path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param], |
|
| 248 |
:format => 'raw' |
|
| 249 |
assert_response :success |
|
| 250 |
# File content |
|
| 251 |
assert @response.body.include?('WITHOUT ANY WARRANTY')
|
|
| 252 |
end |
|
| 253 |
|
|
| 254 |
def test_directory_entry |
|
| 255 |
get :entry, :id => PRJ_ID, |
|
| 256 |
:path => repository_path_hash(['sources'])[:param] |
|
| 257 |
assert_response :success |
|
| 258 |
assert_template 'show' |
|
| 259 |
assert_not_nil assigns(:entry) |
|
| 260 |
assert_equal 'sources', assigns(:entry).name |
|
| 261 |
end |
|
| 262 |
|
|
| 263 |
def test_diff |
|
| 264 |
assert_equal true, @repository.is_default |
|
| 265 |
assert_nil @repository.identifier |
|
| 266 |
assert_equal 0, @repository.changesets.count |
|
| 267 |
@repository.fetch_changesets |
|
| 268 |
@project.reload |
|
| 269 |
assert_equal NUM_REV, @repository.changesets.count |
|
| 270 |
# Full diff of changeset 2f9c0091 |
|
| 271 |
['inline', 'sbs'].each do |dt| |
|
| 272 |
get :diff, |
|
| 273 |
:id => PRJ_ID, |
|
| 274 |
:rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7', |
|
| 275 |
:type => dt |
|
| 276 |
assert_response :success |
|
| 277 |
assert_template 'diff' |
|
| 278 |
# Line 22 removed |
|
| 279 |
assert_tag :tag => 'th', |
|
| 280 |
:content => /22/, |
|
| 281 |
:sibling => { :tag => 'td',
|
|
| 282 |
:attributes => { :class => /diff_out/ },
|
|
| 283 |
:content => /def remove/ } |
|
| 284 |
assert_tag :tag => 'h2', :content => /2f9c0091/ |
|
| 285 |
end |
|
| 286 |
end |
|
| 287 |
|
|
| 288 |
def test_diff_with_rev_and_path |
|
| 289 |
assert_equal 0, @repository.changesets.count |
|
| 290 |
@repository.fetch_changesets |
|
| 291 |
@project.reload |
|
| 292 |
assert_equal NUM_REV, @repository.changesets.count |
|
| 293 |
with_settings :diff_max_lines_displayed => 1000 do |
|
| 294 |
# Full diff of changeset 2f9c0091 |
|
| 295 |
['inline', 'sbs'].each do |dt| |
|
| 296 |
get :diff, |
|
| 297 |
:id => PRJ_ID, |
|
| 298 |
:rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7', |
|
| 299 |
:path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param], |
|
| 300 |
:type => dt |
|
| 301 |
assert_response :success |
|
| 302 |
assert_template 'diff' |
|
| 303 |
# Line 22 removed |
|
| 304 |
assert_tag :tag => 'th', |
|
| 305 |
:content => '22', |
|
| 306 |
:sibling => { :tag => 'td',
|
|
| 307 |
:attributes => { :class => /diff_out/ },
|
|
| 308 |
:content => /def remove/ } |
|
| 309 |
assert_tag :tag => 'h2', :content => /2f9c0091/ |
|
| 310 |
end |
|
| 311 |
end |
|
| 312 |
end |
|
| 313 |
|
|
| 314 |
def test_diff_truncated |
|
| 315 |
assert_equal 0, @repository.changesets.count |
|
| 316 |
@repository.fetch_changesets |
|
| 317 |
@project.reload |
|
| 318 |
assert_equal NUM_REV, @repository.changesets.count |
|
| 319 |
|
|
| 320 |
with_settings :diff_max_lines_displayed => 5 do |
|
| 321 |
# Truncated diff of changeset 2f9c0091 |
|
| 322 |
with_cache do |
|
| 323 |
with_settings :default_language => 'en' do |
|
| 324 |
get :diff, :id => PRJ_ID, :type => 'inline', |
|
| 325 |
:rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' |
|
| 326 |
assert_response :success |
|
| 327 |
assert @response.body.include?("... This diff was truncated")
|
|
| 328 |
end |
|
| 329 |
with_settings :default_language => 'fr' do |
|
| 330 |
get :diff, :id => PRJ_ID, :type => 'inline', |
|
| 331 |
:rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' |
|
| 332 |
assert_response :success |
|
| 333 |
assert ! @response.body.include?("... This diff was truncated")
|
|
| 334 |
assert @response.body.include?("... Ce diff")
|
|
| 335 |
end |
|
| 336 |
end |
|
| 337 |
end |
|
| 338 |
end |
|
| 339 |
|
|
| 340 |
def test_diff_two_revs |
|
| 341 |
assert_equal 0, @repository.changesets.count |
|
| 342 |
@repository.fetch_changesets |
|
| 343 |
@project.reload |
|
| 344 |
assert_equal NUM_REV, @repository.changesets.count |
|
| 345 |
['inline', 'sbs'].each do |dt| |
|
| 346 |
get :diff, |
|
| 347 |
:id => PRJ_ID, |
|
| 348 |
:rev => '61b685fbe55ab05b5ac68402d5720c1a6ac973d1', |
|
| 349 |
:rev_to => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7', |
|
| 350 |
:type => dt |
|
| 351 |
assert_response :success |
|
| 352 |
assert_template 'diff' |
|
| 353 |
diff = assigns(:diff) |
|
| 354 |
assert_not_nil diff |
|
| 355 |
assert_tag :tag => 'h2', :content => /2f9c0091:61b685fb/ |
|
| 356 |
assert_tag :tag => "form", |
|
| 357 |
:attributes => {
|
|
| 358 |
:action => "/projects/subproject1/repository/revisions/" + |
|
| 359 |
"61b685fbe55ab05b5ac68402d5720c1a6ac973d1/diff" |
|
| 360 |
} |
|
| 361 |
assert_tag :tag => 'input', |
|
| 362 |
:attributes => {
|
|
| 363 |
:id => "rev_to", |
|
| 364 |
:name => "rev_to", |
|
| 365 |
:type => "hidden", |
|
| 366 |
:value => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' |
|
| 367 |
} |
|
| 368 |
end |
|
| 369 |
end |
|
| 370 |
|
|
| 371 |
def test_diff_path_in_subrepo |
|
| 372 |
repo = Repository::Git.create( |
|
| 373 |
:project => @project, |
|
| 374 |
:url => REPOSITORY_PATH, |
|
| 375 |
:identifier => 'test-diff-path', |
|
| 376 |
:path_encoding => 'ISO-8859-1' |
|
| 377 |
); |
|
| 378 |
assert repo |
|
| 379 |
assert_equal false, repo.is_default |
|
| 380 |
assert_equal 'test-diff-path', repo.identifier |
|
| 381 |
get :diff, |
|
| 382 |
:id => PRJ_ID, |
|
| 383 |
:repository_id => 'test-diff-path', |
|
| 384 |
:rev => '61b685fbe55ab05b', |
|
| 385 |
:rev_to => '2f9c0091c754a91a', |
|
| 386 |
:type => 'inline' |
|
| 387 |
assert_response :success |
|
| 388 |
assert_template 'diff' |
|
| 389 |
diff = assigns(:diff) |
|
| 390 |
assert_not_nil diff |
|
| 391 |
assert_tag :tag => "form", |
|
| 392 |
:attributes => {
|
|
| 393 |
:action => "/projects/subproject1/repository/test-diff-path/" + |
|
| 394 |
"revisions/61b685fbe55ab05b/diff" |
|
| 395 |
} |
|
| 396 |
assert_tag :tag => 'input', |
|
| 397 |
:attributes => {
|
|
| 398 |
:id => "rev_to", |
|
| 399 |
:name => "rev_to", |
|
| 400 |
:type => "hidden", |
|
| 401 |
:value => '2f9c0091c754a91a' |
|
| 402 |
} |
|
| 403 |
end |
|
| 404 |
|
|
| 405 |
def test_diff_latin_1 |
|
| 406 |
if @ruby19_non_utf8_pass |
|
| 407 |
puts_ruby19_non_utf8_pass() |
|
| 408 |
else |
|
| 409 |
with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do |
|
| 410 |
['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1| |
|
| 411 |
['inline', 'sbs'].each do |dt| |
|
| 412 |
get :diff, :id => PRJ_ID, :rev => r1, :type => dt |
|
| 413 |
assert_response :success |
|
| 414 |
assert_template 'diff' |
|
| 415 |
assert_tag :tag => 'thead', |
|
| 416 |
:descendant => {
|
|
| 417 |
:tag => 'th', |
|
| 418 |
:attributes => { :class => 'filename' } ,
|
|
| 419 |
:content => /latin-1-dir\/test-#{@char_1}.txt/ ,
|
|
| 420 |
}, |
|
| 421 |
:sibling => {
|
|
| 422 |
:tag => 'tbody', |
|
| 423 |
:descendant => {
|
|
| 424 |
:tag => 'td', |
|
| 425 |
:attributes => { :class => /diff_in/ },
|
|
| 426 |
:content => /test-#{@char_1}.txt/
|
|
| 427 |
} |
|
| 428 |
} |
|
| 429 |
end |
|
| 430 |
end |
|
| 431 |
end |
|
| 432 |
end |
|
| 433 |
end |
|
| 434 |
|
|
| 435 |
def test_diff_should_show_filenames |
|
| 436 |
get :diff, :id => PRJ_ID, :rev => 'deff712f05a90d96edbd70facc47d944be5897e3', :type => 'inline' |
|
| 437 |
assert_response :success |
|
| 438 |
assert_template 'diff' |
|
| 439 |
# modified file |
|
| 440 |
assert_select 'th.filename', :text => 'sources/watchers_controller.rb' |
|
| 441 |
# deleted file |
|
| 442 |
assert_select 'th.filename', :text => 'test.txt' |
|
| 443 |
end |
|
| 444 |
|
|
| 445 |
def test_save_diff_type |
|
| 446 |
user1 = User.find(1) |
|
| 447 |
user1.pref[:diff_type] = nil |
|
| 448 |
user1.preference.save |
|
| 449 |
user = User.find(1) |
|
| 450 |
assert_nil user.pref[:diff_type] |
|
| 451 |
|
|
| 452 |
@request.session[:user_id] = 1 # admin |
|
| 453 |
get :diff, |
|
| 454 |
:id => PRJ_ID, |
|
| 455 |
:rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' |
|
| 456 |
assert_response :success |
|
| 457 |
assert_template 'diff' |
|
| 458 |
user.reload |
|
| 459 |
assert_equal "inline", user.pref[:diff_type] |
|
| 460 |
get :diff, |
|
| 461 |
:id => PRJ_ID, |
|
| 462 |
:rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7', |
|
| 463 |
:type => 'sbs' |
|
| 464 |
assert_response :success |
|
| 465 |
assert_template 'diff' |
|
| 466 |
user.reload |
|
| 467 |
assert_equal "sbs", user.pref[:diff_type] |
|
| 468 |
end |
|
| 469 |
|
|
| 470 |
def test_annotate |
|
| 471 |
get :annotate, :id => PRJ_ID, |
|
| 472 |
:path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param] |
|
| 473 |
assert_response :success |
|
| 474 |
assert_template 'annotate' |
|
| 475 |
|
|
| 476 |
# Line 23, changeset 2f9c0091 |
|
| 477 |
assert_select 'tr' do |
|
| 478 |
assert_select 'th.line-num', :text => '23' |
|
| 479 |
assert_select 'td.revision', :text => /2f9c0091/ |
|
| 480 |
assert_select 'td.author', :text => 'jsmith' |
|
| 481 |
assert_select 'td', :text => /remove_watcher/ |
|
| 482 |
end |
|
| 483 |
end |
|
| 484 |
|
|
| 485 |
def test_annotate_at_given_revision |
|
| 486 |
assert_equal 0, @repository.changesets.count |
|
| 487 |
@repository.fetch_changesets |
|
| 488 |
@project.reload |
|
| 489 |
assert_equal NUM_REV, @repository.changesets.count |
|
| 490 |
get :annotate, :id => PRJ_ID, :rev => 'deff7', |
|
| 491 |
:path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param] |
|
| 492 |
assert_response :success |
|
| 493 |
assert_template 'annotate' |
|
| 494 |
assert_tag :tag => 'h2', :content => /@ deff712f/ |
|
| 495 |
end |
|
| 496 |
|
|
| 497 |
def test_annotate_binary_file |
|
| 498 |
get :annotate, :id => PRJ_ID, |
|
| 499 |
:path => repository_path_hash(['images', 'edit.png'])[:param] |
|
| 500 |
assert_response 500 |
|
| 501 |
assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ },
|
|
| 502 |
:content => /cannot be annotated/ |
|
| 503 |
end |
|
| 504 |
|
|
| 505 |
def test_annotate_error_when_too_big |
|
| 506 |
with_settings :file_max_size_displayed => 1 do |
|
| 507 |
get :annotate, :id => PRJ_ID, |
|
| 508 |
:path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param], |
|
| 509 |
:rev => 'deff712f' |
|
| 510 |
assert_response 500 |
|
| 511 |
assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ },
|
|
| 512 |
:content => /exceeds the maximum text file size/ |
|
| 513 |
|
|
| 514 |
get :annotate, :id => PRJ_ID, |
|
| 515 |
:path => repository_path_hash(['README'])[:param], |
|
| 516 |
:rev => '7234cb2' |
|
| 517 |
assert_response :success |
|
| 518 |
assert_template 'annotate' |
|
| 519 |
end |
|
| 520 |
end |
|
| 521 |
|
|
| 522 |
def test_annotate_latin_1 |
|
| 523 |
if @ruby19_non_utf8_pass |
|
| 524 |
puts_ruby19_non_utf8_pass() |
|
| 525 |
elsif WINDOWS_PASS |
|
| 526 |
puts WINDOWS_SKIP_STR |
|
| 527 |
elsif JRUBY_SKIP |
|
| 528 |
puts JRUBY_SKIP_STR |
|
| 529 |
else |
|
| 530 |
with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do |
|
| 531 |
['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1| |
|
| 532 |
get :annotate, :id => PRJ_ID, |
|
| 533 |
:path => repository_path_hash(['latin-1-dir', "test-#{@char_1}.txt"])[:param],
|
|
| 534 |
:rev => r1 |
|
| 535 |
assert_tag :tag => 'th', |
|
| 536 |
:content => '1', |
|
| 537 |
:attributes => { :class => 'line-num' },
|
|
| 538 |
:sibling => { :tag => 'td',
|
|
| 539 |
:content => /test-#{@char_1}.txt/ }
|
|
| 540 |
end |
|
| 541 |
end |
|
| 542 |
end |
|
| 543 |
end |
|
| 544 |
|
|
| 545 |
def test_revisions |
|
| 546 |
assert_equal 0, @repository.changesets.count |
|
| 547 |
@repository.fetch_changesets |
|
| 548 |
@project.reload |
|
| 549 |
assert_equal NUM_REV, @repository.changesets.count |
|
| 550 |
get :revisions, :id => PRJ_ID |
|
| 551 |
assert_response :success |
|
| 552 |
assert_template 'revisions' |
|
| 553 |
assert_tag :tag => 'form', |
|
| 554 |
:attributes => {
|
|
| 555 |
:method => 'get', |
|
| 556 |
:action => '/projects/subproject1/repository/revision' |
|
| 557 |
} |
|
| 558 |
end |
|
| 559 |
|
|
| 560 |
def test_revision |
|
| 561 |
assert_equal 0, @repository.changesets.count |
|
| 562 |
@repository.fetch_changesets |
|
| 563 |
@project.reload |
|
| 564 |
assert_equal NUM_REV, @repository.changesets.count |
|
| 565 |
['61b685fbe55ab05b5ac68402d5720c1a6ac973d1', '61b685f'].each do |r| |
|
| 566 |
get :revision, :id => PRJ_ID, :rev => r |
|
| 567 |
assert_response :success |
|
| 568 |
assert_template 'revision' |
|
| 569 |
end |
|
| 570 |
end |
|
| 571 |
|
|
| 572 |
def test_empty_revision |
|
| 573 |
assert_equal 0, @repository.changesets.count |
|
| 574 |
@repository.fetch_changesets |
|
| 575 |
@project.reload |
|
| 576 |
assert_equal NUM_REV, @repository.changesets.count |
|
| 577 |
['', ' ', nil].each do |r| |
|
| 578 |
get :revision, :id => PRJ_ID, :rev => r |
|
| 579 |
assert_response 404 |
|
| 580 |
assert_error_tag :content => /was not found/ |
|
| 581 |
end |
|
| 582 |
end |
|
| 583 |
|
|
| 584 |
def test_destroy_valid_repository |
|
| 585 |
@request.session[:user_id] = 1 # admin |
|
| 586 |
assert_equal 0, @repository.changesets.count |
|
| 587 |
@repository.fetch_changesets |
|
| 588 |
@project.reload |
|
| 589 |
assert_equal NUM_REV, @repository.changesets.count |
|
| 590 |
|
|
| 591 |
assert_difference 'Repository.count', -1 do |
|
| 592 |
delete :destroy, :id => @repository.id |
|
| 593 |
end |
|
| 594 |
assert_response 302 |
|
| 595 |
@project.reload |
|
| 596 |
assert_nil @project.repository |
|
| 597 |
end |
|
| 598 |
|
|
| 599 |
def test_destroy_invalid_repository |
|
| 600 |
@request.session[:user_id] = 1 # admin |
|
| 601 |
@project.repository.destroy |
|
| 602 |
@repository = Repository::Git.create!( |
|
| 603 |
:project => @project, |
|
| 604 |
:url => "/invalid", |
|
| 605 |
:path_encoding => 'ISO-8859-1' |
|
| 606 |
) |
|
| 607 |
@repository.fetch_changesets |
|
| 608 |
@repository.reload |
|
| 609 |
assert_equal 0, @repository.changesets.count |
|
| 610 |
|
|
| 611 |
assert_difference 'Repository.count', -1 do |
|
| 612 |
delete :destroy, :id => @repository.id |
|
| 613 |
end |
|
| 614 |
assert_response 302 |
|
| 615 |
@project.reload |
|
| 616 |
assert_nil @project.repository |
|
| 617 |
end |
|
| 618 |
|
|
| 619 |
private |
|
| 620 |
|
|
| 621 |
def puts_ruby19_non_utf8_pass |
|
| 622 |
puts "TODO: This test fails in Ruby 1.9 " + |
|
| 623 |
"and Encoding.default_external is not UTF-8. " + |
|
| 624 |
"Current value is '#{Encoding.default_external.to_s}'"
|
|
| 625 |
end |
|
| 626 |
else |
|
| 627 |
puts "Git test repository NOT FOUND. Skipping functional tests !!!" |
|
| 628 |
def test_fake; assert true end |
|
| 629 |
end |
|
| 630 |
|
|
| 631 |
private |
|
| 632 |
def with_cache(&block) |
|
| 633 |
before = ActionController::Base.perform_caching |
|
| 634 |
ActionController::Base.perform_caching = true |
|
| 635 |
block.call |
|
| 636 |
ActionController::Base.perform_caching = before |
|
| 637 |
end |
|
| 638 |
end |
|
| .svn/pristine/74/748d85e16c7b39630792941f331af582630a591a.svn-base | ||
|---|---|---|
| 1 |
/* ***** BEGIN LICENSE BLOCK ***** |
|
| 2 |
* This file is part of DotClear. |
|
| 3 |
* Copyright (c) 2005 Nicolas Martin & Olivier Meunier and contributors. All |
|
| 4 |
* rights reserved. |
|
| 5 |
* |
|
| 6 |
* DotClear is free software; you can redistribute it and/or modify |
|
| 7 |
* it under the terms of the GNU General Public License as published by |
|
| 8 |
* the Free Software Foundation; either version 2 of the License, or |
|
| 9 |
* (at your option) any later version. |
|
| 10 |
* |
|
| 11 |
* DotClear is distributed in the hope that it will be useful, |
|
| 12 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 14 |
* GNU General Public License for more details. |
|
| 15 |
* |
|
| 16 |
* You should have received a copy of the GNU General Public License |
|
| 17 |
* along with DotClear; if not, write to the Free Software |
|
| 18 |
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
| 19 |
* |
|
| 20 |
* ***** END LICENSE BLOCK ***** |
|
| 21 |
*/ |
|
| 22 |
|
|
| 23 |
/* Modified by JP LANG for textile formatting */ |
|
| 24 |
|
|
| 25 |
function jsToolBar(textarea) {
|
|
| 26 |
if (!document.createElement) { return; }
|
|
| 27 |
|
|
| 28 |
if (!textarea) { return; }
|
|
| 29 |
|
|
| 30 |
if ((typeof(document["selection"]) == "undefined") |
|
| 31 |
&& (typeof(textarea["setSelectionRange"]) == "undefined")) {
|
|
| 32 |
return; |
|
| 33 |
} |
|
| 34 |
|
|
| 35 |
this.textarea = textarea; |
|
| 36 |
|
|
| 37 |
this.editor = document.createElement('div');
|
|
| 38 |
this.editor.className = 'jstEditor'; |
|
| 39 |
|
|
| 40 |
this.textarea.parentNode.insertBefore(this.editor,this.textarea); |
|
| 41 |
this.editor.appendChild(this.textarea); |
|
| 42 |
|
|
| 43 |
this.toolbar = document.createElement("div");
|
|
| 44 |
this.toolbar.className = 'jstElements'; |
|
| 45 |
this.editor.parentNode.insertBefore(this.toolbar,this.editor); |
|
| 46 |
|
|
| 47 |
// Dragable resizing |
|
| 48 |
if (this.editor.addEventListener && navigator.appVersion.match(/\bMSIE\b/)) |
|
| 49 |
{
|
|
| 50 |
this.handle = document.createElement('div');
|
|
| 51 |
this.handle.className = 'jstHandle'; |
|
| 52 |
var dragStart = this.resizeDragStart; |
|
| 53 |
var This = this; |
|
| 54 |
this.handle.addEventListener('mousedown',function(event) { dragStart.call(This,event); },false);
|
|
| 55 |
// fix memory leak in Firefox (bug #241518) |
|
| 56 |
window.addEventListener('unload',function() {
|
|
| 57 |
var del = This.handle.parentNode.removeChild(This.handle); |
|
| 58 |
delete(This.handle); |
|
| 59 |
},false); |
|
| 60 |
|
|
| 61 |
this.editor.parentNode.insertBefore(this.handle,this.editor.nextSibling); |
|
| 62 |
} |
|
| 63 |
|
|
| 64 |
this.context = null; |
|
| 65 |
this.toolNodes = {}; // lorsque la toolbar est dessinée , cet objet est garni
|
|
| 66 |
// de raccourcis vers les éléments DOM correspondants aux outils. |
|
| 67 |
} |
|
| 68 |
|
|
| 69 |
function jsButton(title, fn, scope, className) {
|
|
| 70 |
if(typeof jsToolBar.strings == 'undefined') {
|
|
| 71 |
this.title = title || null; |
|
| 72 |
} else {
|
|
| 73 |
this.title = jsToolBar.strings[title] || title || null; |
|
| 74 |
} |
|
| 75 |
this.fn = fn || function(){};
|
|
| 76 |
this.scope = scope || null; |
|
| 77 |
this.className = className || null; |
|
| 78 |
} |
|
| 79 |
jsButton.prototype.draw = function() {
|
|
| 80 |
if (!this.scope) return null; |
|
| 81 |
|
|
| 82 |
var button = document.createElement('button');
|
|
| 83 |
button.setAttribute('type','button');
|
|
| 84 |
button.tabIndex = 200; |
|
| 85 |
if (this.className) button.className = this.className; |
|
| 86 |
button.title = this.title; |
|
| 87 |
var span = document.createElement('span');
|
|
| 88 |
span.appendChild(document.createTextNode(this.title)); |
|
| 89 |
button.appendChild(span); |
|
| 90 |
|
|
| 91 |
if (this.icon != undefined) {
|
|
| 92 |
button.style.backgroundImage = 'url('+this.icon+')';
|
|
| 93 |
} |
|
| 94 |
if (typeof(this.fn) == 'function') {
|
|
| 95 |
var This = this; |
|
| 96 |
button.onclick = function() { try { This.fn.apply(This.scope, arguments) } catch (e) {} return false; };
|
|
| 97 |
} |
|
| 98 |
return button; |
|
| 99 |
} |
|
| 100 |
|
|
| 101 |
function jsSpace(id) {
|
|
| 102 |
this.id = id || null; |
|
| 103 |
this.width = null; |
|
| 104 |
} |
|
| 105 |
jsSpace.prototype.draw = function() {
|
|
| 106 |
var span = document.createElement('span');
|
|
| 107 |
if (this.id) span.id = this.id; |
|
| 108 |
span.appendChild(document.createTextNode(String.fromCharCode(160))); |
|
| 109 |
span.className = 'jstSpacer'; |
|
| 110 |
if (this.width) span.style.marginRight = this.width+'px'; |
|
| 111 |
|
|
| 112 |
return span; |
|
| 113 |
} |
|
| 114 |
|
|
| 115 |
function jsCombo(title, options, scope, fn, className) {
|
|
| 116 |
this.title = title || null; |
|
| 117 |
this.options = options || null; |
|
| 118 |
this.scope = scope || null; |
|
| 119 |
this.fn = fn || function(){};
|
|
| 120 |
this.className = className || null; |
|
| 121 |
} |
|
| 122 |
jsCombo.prototype.draw = function() {
|
|
| 123 |
if (!this.scope || !this.options) return null; |
|
| 124 |
|
|
| 125 |
var select = document.createElement('select');
|
|
| 126 |
if (this.className) select.className = className; |
|
| 127 |
select.title = this.title; |
|
| 128 |
|
|
| 129 |
for (var o in this.options) {
|
|
| 130 |
//var opt = this.options[o]; |
|
| 131 |
var option = document.createElement('option');
|
|
| 132 |
option.value = o; |
|
| 133 |
option.appendChild(document.createTextNode(this.options[o])); |
|
| 134 |
select.appendChild(option); |
|
| 135 |
} |
|
| 136 |
|
|
| 137 |
var This = this; |
|
| 138 |
select.onchange = function() {
|
|
| 139 |
try {
|
|
| 140 |
This.fn.call(This.scope, this.value); |
|
| 141 |
} catch (e) { alert(e); }
|
|
| 142 |
|
|
| 143 |
return false; |
|
| 144 |
} |
|
| 145 |
|
|
| 146 |
return select; |
|
| 147 |
} |
|
| 148 |
|
|
| 149 |
|
|
| 150 |
jsToolBar.prototype = {
|
|
| 151 |
base_url: '', |
|
| 152 |
mode: 'wiki', |
|
| 153 |
elements: {},
|
|
| 154 |
help_link: '', |
|
| 155 |
|
|
| 156 |
getMode: function() {
|
|
| 157 |
return this.mode; |
|
| 158 |
}, |
|
| 159 |
|
|
| 160 |
setMode: function(mode) {
|
|
| 161 |
this.mode = mode || 'wiki'; |
|
| 162 |
}, |
|
| 163 |
|
|
| 164 |
switchMode: function(mode) {
|
|
| 165 |
mode = mode || 'wiki'; |
|
| 166 |
this.draw(mode); |
|
| 167 |
}, |
|
| 168 |
|
|
| 169 |
setHelpLink: function(link) {
|
|
| 170 |
this.help_link = link; |
|
| 171 |
}, |
|
| 172 |
|
|
| 173 |
button: function(toolName) {
|
|
| 174 |
var tool = this.elements[toolName]; |
|
| 175 |
if (typeof tool.fn[this.mode] != 'function') return null; |
|
| 176 |
var b = new jsButton(tool.title, tool.fn[this.mode], this, 'jstb_'+toolName); |
|
| 177 |
if (tool.icon != undefined) b.icon = tool.icon; |
|
| 178 |
return b; |
|
| 179 |
}, |
|
| 180 |
space: function(toolName) {
|
|
| 181 |
var tool = new jsSpace(toolName) |
|
| 182 |
if (this.elements[toolName].width !== undefined) |
|
| 183 |
tool.width = this.elements[toolName].width; |
|
| 184 |
return tool; |
|
| 185 |
}, |
|
| 186 |
combo: function(toolName) {
|
|
| 187 |
var tool = this.elements[toolName]; |
|
| 188 |
var length = tool[this.mode].list.length; |
|
| 189 |
|
|
| 190 |
if (typeof tool[this.mode].fn != 'function' || length == 0) {
|
|
| 191 |
return null; |
|
| 192 |
} else {
|
|
| 193 |
var options = {};
|
|
| 194 |
for (var i=0; i < length; i++) {
|
|
| 195 |
var opt = tool[this.mode].list[i]; |
|
| 196 |
options[opt] = tool.options[opt]; |
|
| 197 |
} |
|
| 198 |
return new jsCombo(tool.title, options, this, tool[this.mode].fn); |
|
| 199 |
} |
|
| 200 |
}, |
|
| 201 |
draw: function(mode) {
|
|
| 202 |
this.setMode(mode); |
|
| 203 |
|
|
| 204 |
// Empty toolbar |
|
| 205 |
while (this.toolbar.hasChildNodes()) {
|
|
| 206 |
this.toolbar.removeChild(this.toolbar.firstChild) |
|
| 207 |
} |
|
| 208 |
this.toolNodes = {}; // vide les raccourcis DOM/**/
|
|
| 209 |
|
|
| 210 |
var h = document.createElement('div');
|
|
| 211 |
h.className = 'help' |
|
| 212 |
h.innerHTML = this.help_link; |
|
| 213 |
'<a href="/help/wiki_syntax.html" onclick="window.open(\'/help/wiki_syntax.html\', \'\', \'resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\'); return false;">Aide</a>'; |
|
| 214 |
this.toolbar.appendChild(h); |
|
| 215 |
|
|
| 216 |
// Draw toolbar elements |
|
| 217 |
var b, tool, newTool; |
|
| 218 |
|
|
| 219 |
for (var i in this.elements) {
|
|
| 220 |
b = this.elements[i]; |
|
| 221 |
|
|
| 222 |
var disabled = |
|
| 223 |
b.type == undefined || b.type == '' |
|
| 224 |
|| (b.disabled != undefined && b.disabled) |
|
| 225 |
|| (b.context != undefined && b.context != null && b.context != this.context); |
|
| 226 |
|
|
| 227 |
if (!disabled && typeof this[b.type] == 'function') {
|
|
| 228 |
tool = this[b.type](i); |
|
| 229 |
if (tool) newTool = tool.draw(); |
|
| 230 |
if (newTool) {
|
|
| 231 |
this.toolNodes[i] = newTool; //mémorise l'accès DOM pour usage éventuel ultérieur |
|
| 232 |
this.toolbar.appendChild(newTool); |
|
| 233 |
} |
|
| 234 |
} |
|
| 235 |
} |
|
| 236 |
}, |
|
| 237 |
|
|
| 238 |
singleTag: function(stag,etag) {
|
|
| 239 |
stag = stag || null; |
|
| 240 |
etag = etag || stag; |
|
| 241 |
|
|
| 242 |
if (!stag || !etag) { return; }
|
|
| 243 |
|
|
| 244 |
this.encloseSelection(stag,etag); |
|
| 245 |
}, |
|
| 246 |
|
|
| 247 |
encloseLineSelection: function(prefix, suffix, fn) {
|
|
| 248 |
this.textarea.focus(); |
|
| 249 |
|
|
| 250 |
prefix = prefix || ''; |
|
| 251 |
suffix = suffix || ''; |
|
| 252 |
|
|
| 253 |
var start, end, sel, scrollPos, subst, res; |
|
| 254 |
|
|
| 255 |
if (typeof(document["selection"]) != "undefined") {
|
|
| 256 |
sel = document.selection.createRange().text; |
|
| 257 |
} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
|
|
| 258 |
start = this.textarea.selectionStart; |
|
| 259 |
end = this.textarea.selectionEnd; |
|
| 260 |
scrollPos = this.textarea.scrollTop; |
|
| 261 |
// go to the start of the line |
|
| 262 |
start = this.textarea.value.substring(0, start).replace(/[^\r\n]*$/g,'').length; |
|
| 263 |
// go to the end of the line |
|
| 264 |
end = this.textarea.value.length - this.textarea.value.substring(end, this.textarea.value.length).replace(/^[^\r\n]*/, '').length; |
|
| 265 |
sel = this.textarea.value.substring(start, end); |
|
| 266 |
} |
|
| 267 |
|
|
| 268 |
if (sel.match(/ $/)) { // exclude ending space char, if any
|
|
| 269 |
sel = sel.substring(0, sel.length - 1); |
|
| 270 |
suffix = suffix + " "; |
|
| 271 |
} |
|
| 272 |
|
|
| 273 |
if (typeof(fn) == 'function') {
|
|
| 274 |
res = (sel) ? fn.call(this,sel) : fn('');
|
|
| 275 |
} else {
|
|
| 276 |
res = (sel) ? sel : ''; |
|
| 277 |
} |
|
| 278 |
|
|
| 279 |
subst = prefix + res + suffix; |
|
| 280 |
|
|
| 281 |
if (typeof(document["selection"]) != "undefined") {
|
|
| 282 |
document.selection.createRange().text = subst; |
|
| 283 |
var range = this.textarea.createTextRange(); |
|
| 284 |
range.collapse(false); |
|
| 285 |
range.move('character', -suffix.length);
|
|
| 286 |
range.select(); |
|
| 287 |
} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
|
|
| 288 |
this.textarea.value = this.textarea.value.substring(0, start) + subst + |
|
| 289 |
this.textarea.value.substring(end); |
|
| 290 |
if (sel) {
|
|
| 291 |
this.textarea.setSelectionRange(start + subst.length, start + subst.length); |
|
| 292 |
} else {
|
|
| 293 |
this.textarea.setSelectionRange(start + prefix.length, start + prefix.length); |
|
| 294 |
} |
|
| 295 |
this.textarea.scrollTop = scrollPos; |
|
| 296 |
} |
|
| 297 |
}, |
|
| 298 |
|
|
| 299 |
encloseSelection: function(prefix, suffix, fn) {
|
|
| 300 |
this.textarea.focus(); |
|
| 301 |
|
|
| 302 |
prefix = prefix || ''; |
|
| 303 |
suffix = suffix || ''; |
|
| 304 |
|
|
| 305 |
var start, end, sel, scrollPos, subst, res; |
|
| 306 |
|
|
| 307 |
if (typeof(document["selection"]) != "undefined") {
|
|
| 308 |
sel = document.selection.createRange().text; |
|
| 309 |
} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
|
|
| 310 |
start = this.textarea.selectionStart; |
|
| 311 |
end = this.textarea.selectionEnd; |
|
| 312 |
scrollPos = this.textarea.scrollTop; |
|
| 313 |
sel = this.textarea.value.substring(start, end); |
|
| 314 |
} |
|
| 315 |
|
|
| 316 |
if (sel.match(/ $/)) { // exclude ending space char, if any
|
|
| 317 |
sel = sel.substring(0, sel.length - 1); |
|
| 318 |
suffix = suffix + " "; |
|
| 319 |
} |
|
| 320 |
|
|
| 321 |
if (typeof(fn) == 'function') {
|
|
| 322 |
res = (sel) ? fn.call(this,sel) : fn('');
|
|
| 323 |
} else {
|
|
| 324 |
res = (sel) ? sel : ''; |
|
| 325 |
} |
|
| 326 |
|
|
| 327 |
subst = prefix + res + suffix; |
|
| 328 |
|
|
| 329 |
if (typeof(document["selection"]) != "undefined") {
|
|
| 330 |
document.selection.createRange().text = subst; |
|
| 331 |
var range = this.textarea.createTextRange(); |
|
| 332 |
range.collapse(false); |
|
| 333 |
range.move('character', -suffix.length);
|
|
| 334 |
range.select(); |
|
| 335 |
// this.textarea.caretPos -= suffix.length; |
|
| 336 |
} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
|
|
| 337 |
this.textarea.value = this.textarea.value.substring(0, start) + subst + |
|
| 338 |
this.textarea.value.substring(end); |
|
| 339 |
if (sel) {
|
|
| 340 |
this.textarea.setSelectionRange(start + subst.length, start + subst.length); |
|
| 341 |
} else {
|
|
| 342 |
this.textarea.setSelectionRange(start + prefix.length, start + prefix.length); |
|
| 343 |
} |
|
| 344 |
this.textarea.scrollTop = scrollPos; |
|
| 345 |
} |
|
| 346 |
}, |
|
| 347 |
|
|
| 348 |
stripBaseURL: function(url) {
|
|
| 349 |
if (this.base_url != '') {
|
|
| 350 |
var pos = url.indexOf(this.base_url); |
|
| 351 |
if (pos == 0) {
|
|
| 352 |
url = url.substr(this.base_url.length); |
|
| 353 |
} |
|
| 354 |
} |
|
| 355 |
|
|
| 356 |
return url; |
|
| 357 |
} |
|
| 358 |
}; |
|
| 359 |
|
|
| 360 |
/** Resizer |
|
| 361 |
-------------------------------------------------------- */ |
|
| 362 |
jsToolBar.prototype.resizeSetStartH = function() {
|
|
| 363 |
this.dragStartH = this.textarea.offsetHeight + 0; |
|
| 364 |
}; |
|
| 365 |
jsToolBar.prototype.resizeDragStart = function(event) {
|
|
| 366 |
var This = this; |
|
| 367 |
this.dragStartY = event.clientY; |
|
| 368 |
this.resizeSetStartH(); |
|
| 369 |
document.addEventListener('mousemove', this.dragMoveHdlr=function(event){This.resizeDragMove(event);}, false);
|
|
| 370 |
document.addEventListener('mouseup', this.dragStopHdlr=function(event){This.resizeDragStop(event);}, false);
|
|
| 371 |
}; |
|
| 372 |
|
|
| 373 |
jsToolBar.prototype.resizeDragMove = function(event) {
|
|
| 374 |
this.textarea.style.height = (this.dragStartH+event.clientY-this.dragStartY)+'px'; |
|
| 375 |
}; |
|
| 376 |
|
|
| 377 |
jsToolBar.prototype.resizeDragStop = function(event) {
|
|
| 378 |
document.removeEventListener('mousemove', this.dragMoveHdlr, false);
|
|
| 379 |
document.removeEventListener('mouseup', this.dragStopHdlr, false);
|
|
| 380 |
}; |
|
| .svn/pristine/74/74ae7eeec3e594a3695dff55d9939fb29749d368.svn-base | ||
|---|---|---|
| 1 |
# encoding: utf-8 |
|
| 2 |
# |
|
| 3 |
# Redmine - project management software |
|
| 4 |
# Copyright (C) 2006-2012 Jean-Philippe Lang |
|
| 5 |
# |
|
| 6 |
# This program is free software; you can redistribute it and/or |
|
| 7 |
# modify it under the terms of the GNU General Public License |
|
| 8 |
# as published by the Free Software Foundation; either version 2 |
|
| 9 |
# of the License, or (at your option) any later version. |
|
| 10 |
# |
|
| 11 |
# This program is distributed in the hope that it will be useful, |
|
| 12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 14 |
# GNU General Public License for more details. |
|
| 15 |
# |
|
| 16 |
# You should have received a copy of the GNU General Public License |
|
| 17 |
# along with this program; if not, write to the Free Software |
|
| 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 19 |
|
|
| 20 |
module AttachmentsHelper |
|
| 21 |
# Displays view/delete links to the attachments of the given object |
|
| 22 |
# Options: |
|
| 23 |
# :author -- author names are not displayed if set to false |
|
| 24 |
# :thumbails -- display thumbnails if enabled in settings |
|
| 25 |
def link_to_attachments(container, options = {})
|
|
| 26 |
options.assert_valid_keys(:author, :thumbnails) |
|
| 27 |
|
|
| 28 |
if container.attachments.any? |
|
| 29 |
options = {:deletable => container.attachments_deletable?, :author => true}.merge(options)
|
|
| 30 |
render :partial => 'attachments/links', |
|
| 31 |
:locals => {:attachments => container.attachments, :options => options, :thumbnails => (options[:thumbnails] && Setting.thumbnails_enabled?)}
|
|
| 32 |
end |
|
| 33 |
end |
|
| 34 |
|
|
| 35 |
def render_api_attachment(attachment, api) |
|
| 36 |
api.attachment do |
|
| 37 |
api.id attachment.id |
|
| 38 |
api.filename attachment.filename |
|
| 39 |
api.filesize attachment.filesize |
|
| 40 |
api.content_type attachment.content_type |
|
| 41 |
api.description attachment.description |
|
| 42 |
api.content_url url_for(:controller => 'attachments', :action => 'download', :id => attachment, :filename => attachment.filename, :only_path => false) |
|
| 43 |
api.author(:id => attachment.author.id, :name => attachment.author.name) if attachment.author |
|
| 44 |
api.created_on attachment.created_on |
|
| 45 |
end |
|
| 46 |
end |
|
| 47 |
end |
|
| .svn/pristine/74/74c1617ddcbb99d4a4a61efc78116f68f0818d6b.svn-base | ||
|---|---|---|
| 1 |
require 'SVG/Graph/Plot' |
|
| 2 |
|
|
| 3 |
module SVG |
|
| 4 |
module Graph |
|
| 5 |
# === For creating SVG plots of scalar temporal data |
|
| 6 |
# |
|
| 7 |
# = Synopsis |
|
| 8 |
# |
|
| 9 |
# require 'SVG/Graph/TimeSeriess' |
|
| 10 |
# |
|
| 11 |
# # Data sets are x,y pairs |
|
| 12 |
# data1 = ["6/17/72", 11, "1/11/72", 7, "4/13/04 17:31", 11, |
|
| 13 |
# "9/11/01", 9, "9/1/85", 2, "9/1/88", 1, "1/15/95", 13] |
|
| 14 |
# data2 = ["8/1/73", 18, "3/1/77", 15, "10/1/98", 4, |
|
| 15 |
# "5/1/02", 14, "3/1/95", 6, "8/1/91", 12, "12/1/87", 6, |
|
| 16 |
# "5/1/84", 17, "10/1/80", 12] |
|
| 17 |
# |
|
| 18 |
# graph = SVG::Graph::TimeSeries.new( {
|
|
| 19 |
# :width => 640, |
|
| 20 |
# :height => 480, |
|
| 21 |
# :graph_title => title, |
|
| 22 |
# :show_graph_title => true, |
|
| 23 |
# :no_css => true, |
|
| 24 |
# :key => true, |
|
| 25 |
# :scale_x_integers => true, |
|
| 26 |
# :scale_y_integers => true, |
|
| 27 |
# :min_x_value => 0, |
|
| 28 |
# :min_y_value => 0, |
|
| 29 |
# :show_data_labels => true, |
|
| 30 |
# :show_x_guidelines => true, |
|
| 31 |
# :show_x_title => true, |
|
| 32 |
# :x_title => "Time", |
|
| 33 |
# :show_y_title => true, |
|
| 34 |
# :y_title => "Ice Cream Cones", |
|
| 35 |
# :y_title_text_direction => :bt, |
|
| 36 |
# :stagger_x_labels => true, |
|
| 37 |
# :x_label_format => "%m/%d/%y", |
|
| 38 |
# }) |
|
| 39 |
# |
|
| 40 |
# graph.add_data({
|
|
| 41 |
# :data => projection |
|
| 42 |
# :title => 'Projected', |
|
| 43 |
# }) |
|
| 44 |
# |
|
| 45 |
# graph.add_data({
|
|
| 46 |
# :data => actual, |
|
| 47 |
# :title => 'Actual', |
|
| 48 |
# }) |
|
| 49 |
# |
|
| 50 |
# print graph.burn() |
|
| 51 |
# |
|
| 52 |
# = Description |
|
| 53 |
# |
|
| 54 |
# Produces a graph of temporal scalar data. |
|
| 55 |
# |
|
| 56 |
# = Examples |
|
| 57 |
# |
|
| 58 |
# http://www.germane-software/repositories/public/SVG/test/timeseries.rb |
|
| 59 |
# |
|
| 60 |
# = Notes |
|
| 61 |
# |
|
| 62 |
# The default stylesheet handles upto 10 data sets, if you |
|
| 63 |
# use more you must create your own stylesheet and add the |
|
| 64 |
# additional settings for the extra data sets. You will know |
|
| 65 |
# if you go over 10 data sets as they will have no style and |
|
| 66 |
# be in black. |
|
| 67 |
# |
|
| 68 |
# Unlike the other types of charts, data sets must contain x,y pairs: |
|
| 69 |
# |
|
| 70 |
# [ "12:30", 2 ] # A data set with 1 point: ("12:30",2)
|
|
| 71 |
# [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and
|
|
| 72 |
# # ("14:20",6)
|
|
| 73 |
# |
|
| 74 |
# Note that multiple data sets within the same chart can differ in length, |
|
| 75 |
# and that the data in the datasets needn't be in order; they will be ordered |
|
| 76 |
# by the plot along the X-axis. |
|
| 77 |
# |
|
| 78 |
# The dates must be parseable by ParseDate, but otherwise can be |
|
| 79 |
# any order of magnitude (seconds within the hour, or years) |
|
| 80 |
# |
|
| 81 |
# = See also |
|
| 82 |
# |
|
| 83 |
# * SVG::Graph::Graph |
|
| 84 |
# * SVG::Graph::BarHorizontal |
|
| 85 |
# * SVG::Graph::Bar |
|
| 86 |
# * SVG::Graph::Line |
|
| 87 |
# * SVG::Graph::Pie |
|
| 88 |
# * SVG::Graph::Plot |
|
| 89 |
# |
|
| 90 |
# == Author |
|
| 91 |
# |
|
| 92 |
# Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom> |
|
| 93 |
# |
|
| 94 |
# Copyright 2004 Sean E. Russell |
|
| 95 |
# This software is available under the Ruby license[LICENSE.txt] |
|
| 96 |
# |
|
| 97 |
class TimeSeries < Plot |
|
| 98 |
# In addition to the defaults set by Graph::initialize and |
|
| 99 |
# Plot::set_defaults, sets: |
|
| 100 |
# [x_label_format] '%Y-%m-%d %H:%M:%S' |
|
| 101 |
# [popup_format] '%Y-%m-%d %H:%M:%S' |
|
| 102 |
def set_defaults |
|
| 103 |
super |
|
| 104 |
init_with( |
|
| 105 |
#:max_time_span => '', |
|
| 106 |
:x_label_format => '%Y-%m-%d %H:%M:%S', |
|
| 107 |
:popup_format => '%Y-%m-%d %H:%M:%S' |
|
| 108 |
) |
|
| 109 |
end |
|
| 110 |
|
|
| 111 |
# The format string use do format the X axis labels. |
|
| 112 |
# See Time::strformat |
|
| 113 |
attr_accessor :x_label_format |
|
| 114 |
# Use this to set the spacing between dates on the axis. The value |
|
| 115 |
# must be of the form |
|
| 116 |
# "\d+ ?(days|weeks|months|years|hours|minutes|seconds)?" |
|
| 117 |
# |
|
| 118 |
# EG: |
|
| 119 |
# |
|
| 120 |
# graph.timescale_divisions = "2 weeks" |
|
| 121 |
# |
|
| 122 |
# will cause the chart to try to divide the X axis up into segments of |
|
| 123 |
# two week periods. |
|
| 124 |
attr_accessor :timescale_divisions |
|
| 125 |
# The formatting used for the popups. See x_label_format |
|
| 126 |
attr_accessor :popup_format |
|
| 127 |
|
|
| 128 |
# Add data to the plot. |
|
| 129 |
# |
|
| 130 |
# d1 = [ "12:30", 2 ] # A data set with 1 point: ("12:30",2)
|
|
| 131 |
# d2 = [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and
|
|
| 132 |
# # ("14:20",6)
|
|
| 133 |
# graph.add_data( |
|
| 134 |
# :data => d1, |
|
| 135 |
# :title => 'One' |
|
| 136 |
# ) |
|
| 137 |
# graph.add_data( |
|
| 138 |
# :data => d2, |
|
| 139 |
# :title => 'Two' |
|
| 140 |
# ) |
|
| 141 |
# |
|
| 142 |
# Note that the data must be in time,value pairs, and that the date format |
|
| 143 |
# may be any date that is parseable by ParseDate. |
|
| 144 |
def add_data data |
|
| 145 |
@data = [] unless @data |
|
| 146 |
|
|
| 147 |
raise "No data provided by #{@data.inspect}" unless data[:data] and
|
|
| 148 |
data[:data].kind_of? Array |
|
| 149 |
raise "Data supplied must be x,y pairs! "+ |
|
| 150 |
"The data provided contained an odd set of "+ |
|
| 151 |
"data points" unless data[:data].length % 2 == 0 |
|
| 152 |
return if data[:data].length == 0 |
|
| 153 |
|
|
| 154 |
|
|
| 155 |
x = [] |
|
| 156 |
y = [] |
|
| 157 |
data[:data].each_index {|i|
|
|
| 158 |
if i%2 == 0 |
|
| 159 |
t = DateTime.parse( data[:data][i] ).to_time |
|
| 160 |
x << t.to_i |
|
| 161 |
else |
|
| 162 |
y << data[:data][i] |
|
| 163 |
end |
|
| 164 |
} |
|
| 165 |
sort( x, y ) |
|
| 166 |
data[:data] = [x,y] |
|
| 167 |
@data << data |
|
| 168 |
end |
|
| 169 |
|
|
| 170 |
|
|
| 171 |
protected |
|
| 172 |
|
|
| 173 |
def min_x_value=(value) |
|
| 174 |
@min_x_value = DateTime.parse( value ).to_time |
|
| 175 |
end |
|
| 176 |
|
|
| 177 |
|
|
| 178 |
def format x, y |
|
| 179 |
Time.at( x ).strftime( popup_format ) |
|
| 180 |
end |
|
| 181 |
|
|
| 182 |
def get_x_labels |
|
| 183 |
get_x_values.collect { |v| Time.at(v).strftime( x_label_format ) }
|
|
| 184 |
end |
|
| 185 |
|
|
| 186 |
private |
|
| 187 |
def get_x_values |
|
| 188 |
rv = [] |
|
| 189 |
min, max, scale_division = x_range |
|
| 190 |
if timescale_divisions |
|
| 191 |
timescale_divisions =~ /(\d+) ?(day|week|month|year|hour|minute|second)?/ |
|
| 192 |
division_units = $2 ? $2 : "day" |
|
| 193 |
amount = $1.to_i |
|
| 194 |
if amount |
|
| 195 |
step = nil |
|
| 196 |
case division_units |
|
| 197 |
when "month" |
|
| 198 |
cur = min |
|
| 199 |
while cur < max |
|
| 200 |
rv << cur |
|
| 201 |
arr = Time.at( cur ).to_a |
|
| 202 |
arr[4] += amount |
|
| 203 |
if arr[4] > 12 |
|
| 204 |
arr[5] += (arr[4] / 12).to_i |
|
| 205 |
arr[4] = (arr[4] % 12) |
|
| 206 |
end |
|
| 207 |
cur = Time.local(*arr).to_i |
|
| 208 |
end |
|
| 209 |
when "year" |
|
| 210 |
cur = min |
|
| 211 |
while cur < max |
|
| 212 |
rv << cur |
|
| 213 |
arr = Time.at( cur ).to_a |
|
| 214 |
arr[5] += amount |
|
| 215 |
cur = Time.local(*arr).to_i |
|
| 216 |
end |
|
| 217 |
when "week" |
|
| 218 |
step = 7 * 24 * 60 * 60 * amount |
|
| 219 |
when "day" |
|
| 220 |
step = 24 * 60 * 60 * amount |
|
| 221 |
when "hour" |
|
| 222 |
step = 60 * 60 * amount |
|
| 223 |
when "minute" |
|
| 224 |
step = 60 * amount |
|
| 225 |
when "second" |
|
| 226 |
step = amount |
|
| 227 |
end |
|
| 228 |
min.step( max, step ) {|v| rv << v} if step
|
|
| 229 |
|
|
| 230 |
return rv |
|
| 231 |
end |
|
| 232 |
end |
|
| 233 |
min.step( max, scale_division ) {|v| rv << v}
|
|
| 234 |
return rv |
|
| 235 |
end |
|
| 236 |
end |
|
| 237 |
end |
|
| 238 |
end |
|
Also available in: Unified diff