hash_ext_test.rb 60.9 KB
Newer Older
1
require 'abstract_unit'
J
Jeremy Kemper 已提交
2
require 'active_support/core_ext/hash'
3 4
require 'bigdecimal'
require 'active_support/core_ext/string/access'
5
require 'active_support/ordered_hash'
6
require 'active_support/core_ext/object/conversions'
7
require 'active_support/core_ext/object/deep_dup'
8
require 'active_support/inflections'
9

10
class HashExtTest < ActiveSupport::TestCase
11
  class IndifferentHash < ActiveSupport::HashWithIndifferentAccess
12
  end
13

14 15
  class SubclassingArray < Array
  end
16

17 18 19
  class SubclassingHash < Hash
  end

20 21 22 23 24 25
  class NonIndifferentHash < Hash
    def nested_under_indifferent_access
      self
    end
  end

26 27 28 29 30 31 32 33 34 35
  class HashByConversion
    def initialize(hash)
      @hash = hash
    end

    def to_hash
      @hash
    end
  end

36 37
  def setup
    @strings = { 'a' => 1, 'b' => 2 }
38
    @nested_strings = { 'a' => { 'b' => { 'c' => 3 } } }
39
    @symbols = { :a  => 1, :b  => 2 }
40
    @nested_symbols = { :a => { :b => { :c => 3 } } }
41
    @mixed   = { :a  => 1, 'b' => 2 }
42
    @nested_mixed   = { 'a' => { :b => { 'c' => 3 } } }
43
    @fixnums = {  0  => 1,  1  => 2 }
44
    @nested_fixnums = {  0  => { 1  => { 2 => 3} } }
45
    @illegal_symbols = { [] => 3 }
46
    @nested_illegal_symbols = { [] => { [] => 3} }
47 48
    @upcase_strings = { 'A' => 1, 'B' => 2 }
    @nested_upcase_strings = { 'A' => { 'B' => { 'C' => 3 } } }
49 50 51 52
    @string_array_of_hashes = { 'a' => [ { 'b' => 2 }, { 'c' => 3 }, 4 ] }
    @symbol_array_of_hashes = { :a => [ { :b => 2 }, { :c => 3 }, 4 ] }
    @mixed_array_of_hashes = { :a => [ { :b => 2 }, { 'c' => 3 }, 4 ] }
    @upcase_array_of_hashes = { 'A' => [ { 'B' => 2 }, { 'C' => 3 }, 4 ] }
53 54 55 56
  end

  def test_methods
    h = {}
57 58
    assert_respond_to h, :transform_keys
    assert_respond_to h, :transform_keys!
59 60
    assert_respond_to h, :deep_transform_keys
    assert_respond_to h, :deep_transform_keys!
61 62
    assert_respond_to h, :symbolize_keys
    assert_respond_to h, :symbolize_keys!
63 64
    assert_respond_to h, :deep_symbolize_keys
    assert_respond_to h, :deep_symbolize_keys!
65 66
    assert_respond_to h, :stringify_keys
    assert_respond_to h, :stringify_keys!
67 68
    assert_respond_to h, :deep_stringify_keys
    assert_respond_to h, :deep_stringify_keys!
69 70
    assert_respond_to h, :to_options
    assert_respond_to h, :to_options!
71 72
    assert_respond_to h, :compact
    assert_respond_to h, :compact!
73 74
    assert_respond_to h, :except
    assert_respond_to h, :except!
75 76
  end

77 78 79 80 81 82
  def test_transform_keys
    assert_equal @upcase_strings, @strings.transform_keys{ |key| key.to_s.upcase }
    assert_equal @upcase_strings, @symbols.transform_keys{ |key| key.to_s.upcase }
    assert_equal @upcase_strings, @mixed.transform_keys{ |key| key.to_s.upcase }
  end

83 84 85 86 87 88
  def test_transform_keys_not_mutates
    transformed_hash = @mixed.dup
    transformed_hash.transform_keys{ |key| key.to_s.upcase }
    assert_equal @mixed, transformed_hash
  end

89 90 91 92
  def test_deep_transform_keys
    assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys{ |key| key.to_s.upcase }
    assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys{ |key| key.to_s.upcase }
    assert_equal @nested_upcase_strings, @nested_mixed.deep_transform_keys{ |key| key.to_s.upcase }
93 94 95
    assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }
    assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }
    assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }
96 97
  end

98 99 100 101 102 103
  def test_deep_transform_keys_not_mutates
    transformed_hash = @nested_mixed.deep_dup
    transformed_hash.deep_transform_keys{ |key| key.to_s.upcase }
    assert_equal @nested_mixed, transformed_hash
  end

104 105 106 107 108 109
  def test_transform_keys!
    assert_equal @upcase_strings, @symbols.dup.transform_keys!{ |key| key.to_s.upcase }
    assert_equal @upcase_strings, @strings.dup.transform_keys!{ |key| key.to_s.upcase }
    assert_equal @upcase_strings, @mixed.dup.transform_keys!{ |key| key.to_s.upcase }
  end

110 111 112 113 114 115 116
  def test_transform_keys_with_bang_mutates
    transformed_hash = @mixed.dup
    transformed_hash.transform_keys!{ |key| key.to_s.upcase }
    assert_equal @upcase_strings, transformed_hash
    assert_equal @mixed, { :a => 1, "b" => 2 }
  end

117
  def test_deep_transform_keys!
118 119 120
    assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
    assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
    assert_equal @nested_upcase_strings, @nested_mixed.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
121 122 123
    assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
    assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
    assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
124 125 126 127 128 129 130 131
  end

  def test_deep_transform_keys_with_bang_mutates
    transformed_hash = @nested_mixed.deep_dup
    transformed_hash.deep_transform_keys!{ |key| key.to_s.upcase }
    assert_equal @nested_upcase_strings, transformed_hash
    assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } }
  end
132

133 134 135 136 137 138
  def test_symbolize_keys
    assert_equal @symbols, @symbols.symbolize_keys
    assert_equal @symbols, @strings.symbolize_keys
    assert_equal @symbols, @mixed.symbolize_keys
  end

139 140 141 142 143 144
  def test_symbolize_keys_not_mutates
    transformed_hash = @mixed.dup
    transformed_hash.symbolize_keys
    assert_equal @mixed, transformed_hash
  end

145 146 147 148
  def test_deep_symbolize_keys
    assert_equal @nested_symbols, @nested_symbols.deep_symbolize_keys
    assert_equal @nested_symbols, @nested_strings.deep_symbolize_keys
    assert_equal @nested_symbols, @nested_mixed.deep_symbolize_keys
149 150 151
    assert_equal @symbol_array_of_hashes, @string_array_of_hashes.deep_symbolize_keys
    assert_equal @symbol_array_of_hashes, @symbol_array_of_hashes.deep_symbolize_keys
    assert_equal @symbol_array_of_hashes, @mixed_array_of_hashes.deep_symbolize_keys
152 153
  end

154 155 156 157 158 159
  def test_deep_symbolize_keys_not_mutates
    transformed_hash = @nested_mixed.deep_dup
    transformed_hash.deep_symbolize_keys
    assert_equal @nested_mixed, transformed_hash
  end

160 161 162 163
  def test_symbolize_keys!
    assert_equal @symbols, @symbols.dup.symbolize_keys!
    assert_equal @symbols, @strings.dup.symbolize_keys!
    assert_equal @symbols, @mixed.dup.symbolize_keys!
164
  end
165

166 167 168 169 170 171 172
  def test_symbolize_keys_with_bang_mutates
    transformed_hash = @mixed.dup
    transformed_hash.deep_symbolize_keys!
    assert_equal @symbols, transformed_hash
    assert_equal @mixed, { :a => 1, "b" => 2 }
  end

173
  def test_deep_symbolize_keys!
174 175 176
    assert_equal @nested_symbols, @nested_symbols.deep_dup.deep_symbolize_keys!
    assert_equal @nested_symbols, @nested_strings.deep_dup.deep_symbolize_keys!
    assert_equal @nested_symbols, @nested_mixed.deep_dup.deep_symbolize_keys!
177 178 179
    assert_equal @symbol_array_of_hashes, @string_array_of_hashes.deep_dup.deep_symbolize_keys!
    assert_equal @symbol_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_symbolize_keys!
    assert_equal @symbol_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_symbolize_keys!
180 181 182 183 184 185 186
  end

  def test_deep_symbolize_keys_with_bang_mutates
    transformed_hash = @nested_mixed.deep_dup
    transformed_hash.deep_symbolize_keys!
    assert_equal @nested_symbols, transformed_hash
    assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } }
187 188
  end

189 190 191
  def test_symbolize_keys_preserves_keys_that_cant_be_symbolized
    assert_equal @illegal_symbols, @illegal_symbols.symbolize_keys
    assert_equal @illegal_symbols, @illegal_symbols.dup.symbolize_keys!
192 193
  end

194 195
  def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized
    assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_symbolize_keys
196
    assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_dup.deep_symbolize_keys!
197 198
  end

199 200 201 202 203
  def test_symbolize_keys_preserves_fixnum_keys
    assert_equal @fixnums, @fixnums.symbolize_keys
    assert_equal @fixnums, @fixnums.dup.symbolize_keys!
  end

204 205
  def test_deep_symbolize_keys_preserves_fixnum_keys
    assert_equal @nested_fixnums, @nested_fixnums.deep_symbolize_keys
206
    assert_equal @nested_fixnums, @nested_fixnums.deep_dup.deep_symbolize_keys!
207 208
  end

209 210 211 212 213 214
  def test_stringify_keys
    assert_equal @strings, @symbols.stringify_keys
    assert_equal @strings, @strings.stringify_keys
    assert_equal @strings, @mixed.stringify_keys
  end

215 216 217 218 219 220
  def test_stringify_keys_not_mutates
    transformed_hash = @mixed.dup
    transformed_hash.stringify_keys
    assert_equal @mixed, transformed_hash
  end

221 222 223 224
  def test_deep_stringify_keys
    assert_equal @nested_strings, @nested_symbols.deep_stringify_keys
    assert_equal @nested_strings, @nested_strings.deep_stringify_keys
    assert_equal @nested_strings, @nested_mixed.deep_stringify_keys
225 226 227
    assert_equal @string_array_of_hashes, @string_array_of_hashes.deep_stringify_keys
    assert_equal @string_array_of_hashes, @symbol_array_of_hashes.deep_stringify_keys
    assert_equal @string_array_of_hashes, @mixed_array_of_hashes.deep_stringify_keys
228 229
  end

230 231 232 233 234 235
  def test_deep_stringify_keys_not_mutates
    transformed_hash = @nested_mixed.deep_dup
    transformed_hash.deep_stringify_keys
    assert_equal @nested_mixed, transformed_hash
  end

236 237 238 239 240 241
  def test_stringify_keys!
    assert_equal @strings, @symbols.dup.stringify_keys!
    assert_equal @strings, @strings.dup.stringify_keys!
    assert_equal @strings, @mixed.dup.stringify_keys!
  end

242 243 244 245 246 247 248
  def test_stringify_keys_with_bang_mutates
    transformed_hash = @mixed.dup
    transformed_hash.stringify_keys!
    assert_equal @strings, transformed_hash
    assert_equal @mixed, { :a => 1, "b" => 2 }
  end

249
  def test_deep_stringify_keys!
250 251 252
    assert_equal @nested_strings, @nested_symbols.deep_dup.deep_stringify_keys!
    assert_equal @nested_strings, @nested_strings.deep_dup.deep_stringify_keys!
    assert_equal @nested_strings, @nested_mixed.deep_dup.deep_stringify_keys!
253 254 255
    assert_equal @string_array_of_hashes, @string_array_of_hashes.deep_dup.deep_stringify_keys!
    assert_equal @string_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_stringify_keys!
    assert_equal @string_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_stringify_keys!
256 257 258 259 260 261 262
  end

  def test_deep_stringify_keys_with_bang_mutates
    transformed_hash = @nested_mixed.deep_dup
    transformed_hash.deep_stringify_keys!
    assert_equal @nested_strings, transformed_hash
    assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } }
263 264
  end

265
  def test_symbolize_keys_for_hash_with_indifferent_access
266
    assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys
267 268 269 270 271
    assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys
    assert_equal @symbols, @strings.with_indifferent_access.symbolize_keys
    assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys
  end

272 273 274 275 276 277 278 279
  def test_deep_symbolize_keys_for_hash_with_indifferent_access
    assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys
    assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys
    assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys
    assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys
  end


280 281 282 283 284 285
  def test_symbolize_keys_bang_for_hash_with_indifferent_access
    assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! }
    assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! }
    assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! }
  end

286
  def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access
287 288 289
    assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
    assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! }
    assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! }
290 291
  end

292 293 294 295 296
  def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
    assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys
    assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! }
  end

297 298
  def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
    assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys
299
    assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
300 301
  end

302 303 304 305 306
  def test_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
    assert_equal @fixnums, @fixnums.with_indifferent_access.symbolize_keys
    assert_raise(NoMethodError) { @fixnums.with_indifferent_access.dup.symbolize_keys! }
  end

307 308
  def test_deep_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
    assert_equal @nested_fixnums, @nested_fixnums.with_indifferent_access.deep_symbolize_keys
309
    assert_raise(NoMethodError) { @nested_fixnums.with_indifferent_access.deep_dup.deep_symbolize_keys! }
310 311
  end

312
  def test_stringify_keys_for_hash_with_indifferent_access
313
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys
314 315 316 317 318
    assert_equal @strings, @symbols.with_indifferent_access.stringify_keys
    assert_equal @strings, @strings.with_indifferent_access.stringify_keys
    assert_equal @strings, @mixed.with_indifferent_access.stringify_keys
  end

319 320 321 322 323 324 325
  def test_deep_stringify_keys_for_hash_with_indifferent_access
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys
    assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys
    assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys
    assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys
  end

326
  def test_stringify_keys_bang_for_hash_with_indifferent_access
327
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys!
328 329 330
    assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys!
    assert_equal @strings, @strings.with_indifferent_access.dup.stringify_keys!
    assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
331 332
  end

333 334
  def test_deep_stringify_keys_bang_for_hash_with_indifferent_access
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys!
335 336 337
    assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys!
    assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys!
    assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys!
338 339
  end

340 341 342 343 344 345
  def test_nested_under_indifferent_access
    foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
    assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"]

    foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
    assert_kind_of NonIndifferentHash, foo["foo"]
346 347 348

    foo = { "foo" => IndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
    assert_kind_of IndifferentHash, foo["foo"]
349 350
  end

351
  def test_indifferent_assorted
352 353 354
    @strings = @strings.with_indifferent_access
    @symbols = @symbols.with_indifferent_access
    @mixed   = @mixed.with_indifferent_access
355

356
    assert_equal 'a', @strings.__send__(:convert_key, :a)
357

358 359 360
    assert_equal 1, @strings.fetch('a')
    assert_equal 1, @strings.fetch(:a.to_s)
    assert_equal 1, @strings.fetch(:a)
361

362
    hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
363 364 365
    method_map = { :'[]' => 1, :fetch => 1, :values_at => [1],
      :has_key? => true, :include? => true, :key? => true,
      :member? => true }
366

367
    hashes.each do |name, hash|
368
      method_map.sort_by(&:to_s).each do |meth, expected|
369
        assert_equal(expected, hash.__send__(meth, 'a'),
370
                     "Calling #{name}.#{meth} 'a'")
371
        assert_equal(expected, hash.__send__(meth, :a),
372
                     "Calling #{name}.#{meth} :a")
373 374
      end
    end
375

376 377 378 379 380 381
    assert_equal [1, 2], @strings.values_at('a', 'b')
    assert_equal [1, 2], @strings.values_at(:a, :b)
    assert_equal [1, 2], @symbols.values_at('a', 'b')
    assert_equal [1, 2], @symbols.values_at(:a, :b)
    assert_equal [1, 2], @mixed.values_at('a', 'b')
    assert_equal [1, 2], @mixed.values_at(:a, :b)
382
  end
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411

  def test_indifferent_reading
    hash = HashWithIndifferentAccess.new
    hash["a"] = 1
    hash["b"] = true
    hash["c"] = false
    hash["d"] = nil

    assert_equal 1, hash[:a]
    assert_equal true, hash[:b]
    assert_equal false, hash[:c]
    assert_equal nil, hash[:d]
    assert_equal nil, hash[:e]
  end

  def test_indifferent_reading_with_nonnil_default
    hash = HashWithIndifferentAccess.new(1)
    hash["a"] = 1
    hash["b"] = true
    hash["c"] = false
    hash["d"] = nil

    assert_equal 1, hash[:a]
    assert_equal true, hash[:b]
    assert_equal false, hash[:c]
    assert_equal nil, hash[:d]
    assert_equal 1, hash[:e]
  end

412 413 414 415 416
  def test_indifferent_writing
    hash = HashWithIndifferentAccess.new
    hash[:a] = 1
    hash['b'] = 2
    hash[3] = 3
417

418 419 420 421 422
    assert_equal hash['a'], 1
    assert_equal hash['b'], 2
    assert_equal hash[:a], 1
    assert_equal hash[:b], 2
    assert_equal hash[3], 3
423 424
  end

425 426 427 428
  def test_indifferent_update
    hash = HashWithIndifferentAccess.new
    hash[:a] = 'a'
    hash['b'] = 'b'
429

430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
    updated_with_strings = hash.update(@strings)
    updated_with_symbols = hash.update(@symbols)
    updated_with_mixed = hash.update(@mixed)

    assert_equal updated_with_strings[:a], 1
    assert_equal updated_with_strings['a'], 1
    assert_equal updated_with_strings['b'], 2

    assert_equal updated_with_symbols[:a], 1
    assert_equal updated_with_symbols['b'], 2
    assert_equal updated_with_symbols[:b], 2

    assert_equal updated_with_mixed[:a], 1
    assert_equal updated_with_mixed['b'], 2

445
    assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
446 447
  end

448 449 450 451 452 453
  def test_update_with_to_hash_conversion
    hash = HashWithIndifferentAccess.new
    hash.update HashByConversion.new({ :a => 1 })
    assert_equal hash['a'], 1
  end

454 455 456 457
  def test_indifferent_merging
    hash = HashWithIndifferentAccess.new
    hash[:a] = 'failure'
    hash['b'] = 'failure'
458

459
    other = { 'a' => 1, :b => 2 }
460

461
    merged = hash.merge(other)
462

463 464 465
    assert_equal HashWithIndifferentAccess, merged.class
    assert_equal 1, merged[:a]
    assert_equal 2, merged['b']
466

467
    hash.update(other)
468

469 470 471
    assert_equal 1, hash[:a]
    assert_equal 2, hash['b']
  end
472

473 474 475 476 477 478
  def test_merge_with_to_hash_conversion
    hash = HashWithIndifferentAccess.new
    merged = hash.merge HashByConversion.new({ :a => 1 })
    assert_equal merged['a'], 1
  end

479 480 481 482 483 484 485 486 487 488 489 490
  def test_indifferent_replace
    hash = HashWithIndifferentAccess.new
    hash[:a] = 42

    replaced = hash.replace(b: 12)

    assert hash.key?('b')
    assert !hash.key?(:a)
    assert_equal 12, hash[:b]
    assert_same hash, replaced
  end

491 492 493 494 495 496 497 498 499 500 501 502
  def test_replace_with_to_hash_conversion
    hash = HashWithIndifferentAccess.new
    hash[:a] = 42

    replaced = hash.replace(HashByConversion.new(b: 12))

    assert hash.key?('b')
    assert !hash.key?(:a)
    assert_equal 12, hash[:b]
    assert_same hash, replaced
  end

503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
  def test_indifferent_merging_with_block
    hash = HashWithIndifferentAccess.new
    hash[:a] = 1
    hash['b'] = 3

    other = { 'a' => 4, :b => 2, 'c' => 10 }

    merged = hash.merge(other) { |key, old, new| old > new ? old : new }

    assert_equal HashWithIndifferentAccess, merged.class
    assert_equal 4, merged[:a]
    assert_equal 3, merged['b']
    assert_equal 10, merged[:c]

    other_indifferent = HashWithIndifferentAccess.new('a' => 9, :b => 2)

    merged = hash.merge(other_indifferent) { |key, old, new| old + new }

    assert_equal HashWithIndifferentAccess, merged.class
    assert_equal 10, merged[:a]
    assert_equal 5, merged[:b]
  end

526
  def test_indifferent_reverse_merging
527 528 529 530
    hash = HashWithIndifferentAccess.new key: :old_value
    hash.reverse_merge! key: :new_value
    assert_equal :old_value, hash[:key]

531 532 533 534 535 536
    hash = HashWithIndifferentAccess.new('some' => 'value', 'other' => 'value')
    hash.reverse_merge!(:some => 'noclobber', :another => 'clobber')
    assert_equal 'value', hash[:some]
    assert_equal 'clobber', hash[:another]
  end

537 538 539 540 541 542 543 544 545 546
  def test_indifferent_deleting
    get_hash = proc{ { :a => 'foo' }.with_indifferent_access }
    hash = get_hash.call
    assert_equal hash.delete(:a), 'foo'
    assert_equal hash.delete(:a), nil
    hash = get_hash.call
    assert_equal hash.delete('a'), 'foo'
    assert_equal hash.delete('a'), nil
  end

547 548 549 550 551
  def test_indifferent_select
    hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| v == 1}

    assert_equal({ 'a' => 1 }, hash)
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
552 553
  end

554 555 556 557 558
  def test_indifferent_select_returns_enumerator
    enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).select
    assert_instance_of Enumerator, enum
  end

559 560 561 562
  def test_indifferent_select_returns_a_hash_when_unchanged
    hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| true}

    assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579
  end

  def test_indifferent_select_bang
    indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
    indifferent_strings.select! {|k,v| v == 1}

    assert_equal({ 'a' => 1 }, indifferent_strings)
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
  end

  def test_indifferent_reject
    hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject {|k,v| v != 1}

    assert_equal({ 'a' => 1 }, hash)
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
  end

580 581 582 583 584
  def test_indifferent_reject_returns_enumerator
    enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject
    assert_instance_of Enumerator, enum
  end

585 586 587 588 589 590 591 592
  def test_indifferent_reject_bang
    indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
    indifferent_strings.reject! {|k,v| v != 1}

    assert_equal({ 'a' => 1 }, indifferent_strings)
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
  end

593 594 595 596 597 598 599 600 601 602
  def test_indifferent_to_hash
    # Should convert to a Hash with String keys.
    assert_equal @strings, @mixed.with_indifferent_access.to_hash

    # Should preserve the default value.
    mixed_with_default = @mixed.dup
    mixed_with_default.default = '1234'
    roundtrip = mixed_with_default.with_indifferent_access.to_hash
    assert_equal @strings, roundtrip
    assert_equal '1234', roundtrip.default
603 604

    # Ensure nested hashes are not HashWithIndiffereneAccess
605 606 607 608
    new_to_hash = @nested_mixed.with_indifferent_access.to_hash
    assert_not new_to_hash.instance_of?(HashWithIndifferentAccess)
    assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess)
    assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess)
609 610
  end

611 612 613 614 615 616 617
  def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
    hash = HashWithIndifferentAccess.new {|h, k| h[k] = []}
    hash[:a] << 1

    assert_equal [1], hash[:a]
  end

618 619 620 621 622 623 624
  def test_with_indifferent_access_has_no_side_effects_on_existing_hash
    hash = {content: [{:foo => :bar, 'bar' => 'baz'}]}
    hash.with_indifferent_access

    assert_equal [:foo, "bar"], hash[:content].first.keys
  end

625 626 627
  def test_indifferent_hash_with_array_of_hashes
    hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access
    assert_equal "1", hash[:urls][:url].first[:address]
628 629 630 631 632

    hash = hash.to_hash
    assert_not hash.instance_of?(HashWithIndifferentAccess)
    assert_not hash["urls"].instance_of?(HashWithIndifferentAccess)
    assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess)
633
  end
634

635 636 637 638 639 640
  def test_should_preserve_array_subclass_when_value_is_array
    array = SubclassingArray.new
    array << { "address" => "1" }
    hash = { "urls" => { "url" => array }}.with_indifferent_access
    assert_equal SubclassingArray, hash[:urls][:url].class
  end
641

642 643 644 645 646 647
  def test_should_preserve_array_class_when_hash_value_is_frozen_array
    array = SubclassingArray.new
    array << { "address" => "1" }
    hash = { "urls" => { "url" => array.freeze }}.with_indifferent_access
    assert_equal SubclassingArray, hash[:urls][:url].class
  end
648

649 650 651
  def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash
    h = HashWithIndifferentAccess.new
    h[:first] = 1
652
    h = h.stringify_keys
653 654 655
    assert_equal 1, h['first']
    h = HashWithIndifferentAccess.new
    h['first'] = 1
656
    h = h.symbolize_keys
657 658 659
    assert_equal 1, h[:first]
  end

660 661 662 663 664 665 666 667 668 669 670
  def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash
    h = HashWithIndifferentAccess.new
    h[:first] = 1
    h = h.deep_stringify_keys
    assert_equal 1, h['first']
    h = HashWithIndifferentAccess.new
    h['first'] = 1
    h = h.deep_symbolize_keys
    assert_equal 1, h[:first]
  end

671 672 673 674 675 676 677
  def test_to_options_on_indifferent_preserves_hash
    h = HashWithIndifferentAccess.new
    h['first'] = 1
    h.to_options!
    assert_equal 1, h['first']
  end

678 679 680 681 682 683 684 685
  def test_to_options_on_indifferent_preserves_works_as_hash_with_dup
    h = HashWithIndifferentAccess.new({ a: { b: 'b' } })
    dup = h.dup

    dup[:a][:c] = 'c'
    assert_equal 'c', h[:a][:c]
  end

A
Akshay Vishnoi 已提交
686
  def test_indifferent_sub_hashes
687 688 689 690 691 692 693
    h = {'user' => {'id' => 5}}.with_indifferent_access
    ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}

    h = {:user => {:id => 5}}.with_indifferent_access
    ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
  end

694 695 696 697 698 699 700 701 702 703 704
  def test_indifferent_duplication
    # Should preserve default value
    h = HashWithIndifferentAccess.new
    h.default = '1234'
    assert_equal h.default, h.dup.default

    # Should preserve class for subclasses
    h = IndifferentHash.new
    assert_equal h.class, h.dup.class
  end

705 706 707
  def test_assert_valid_keys
    assert_nothing_raised do
      { :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
708
      { :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
709
    end
710 711 712 713 714
    # not all valid keys are required to be present
    assert_nothing_raised do
      { :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny, :sunny ])
      { :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny, :sunny)
    end
715

716
    exception = assert_raise ArgumentError do
717
      { :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
718 719 720 721
    end
    assert_equal "Unknown key: :failore. Valid keys are: :failure, :funny", exception.message

    exception = assert_raise ArgumentError do
722
      { :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
723
    end
724
    assert_equal "Unknown key: :failore. Valid keys are: :failure, :funny", exception.message
725

726 727 728 729 730 731 732 733 734
    exception = assert_raise ArgumentError do
      { :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure ])
    end
    assert_equal "Unknown key: :failore. Valid keys are: :failure", exception.message

    exception = assert_raise ArgumentError do
      { :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure)
    end
    assert_equal "Unknown key: :failore. Valid keys are: :failure", exception.message
735
  end
736

737 738 739 740 741
  def test_assorted_keys_not_stringified
    original = {Object.new => 2, 1 => 2, [] => true}
    indiff = original.with_indifferent_access
    assert(!indiff.keys.any? {|k| k.kind_of? String}, "A key was converted to a string!")
  end
742

743 744 745 746 747 748 749 750 751 752
  def test_deep_merge
    hash_1 = { :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } }
    hash_2 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }
    expected = { :a => 1, :b => "b", :c => { :c1 => 2, :c2 => "c2", :c3 => { :d1 => "d1", :d2 => "d2" } } }
    assert_equal expected, hash_1.deep_merge(hash_2)

    hash_1.deep_merge!(hash_2)
    assert_equal expected, hash_1
  end

753 754 755 756 757 758 759 760 761 762
  def test_deep_merge_with_block
    hash_1 = { :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } }
    hash_2 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }
    expected = { :a => [:a, "a", 1], :b => "b", :c => { :c1 => [:c1, "c1", 2], :c2 => "c2", :c3 => { :d1 => "d1", :d2 => "d2" } } }
    assert_equal(expected, hash_1.deep_merge(hash_2) { |k,o,n| [k, o, n] })

    hash_1.deep_merge!(hash_2) { |k,o,n| [k, o, n] }
    assert_equal expected, hash_1
  end

763 764 765 766
  def test_deep_merge_with_falsey_values
    hash_1 = { e: false }
    hash_2 = { e: 'e' }
    expected = { e: [:e, false, 'e'] }
767
    assert_equal(expected, hash_1.deep_merge(hash_2) { |k, o, n| [k, o, n] })
768

769
    hash_1.deep_merge!(hash_2) { |k, o, n| [k, o, n] }
770 771 772
    assert_equal expected, hash_1
  end

773 774 775 776 777 778 779 780 781 782 783 784
  def test_deep_merge_on_indifferent_access
    hash_1 = HashWithIndifferentAccess.new({ :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } })
    hash_2 = HashWithIndifferentAccess.new({ :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } })
    hash_3 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }
    expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } }
    assert_equal expected, hash_1.deep_merge(hash_2)
    assert_equal expected, hash_1.deep_merge(hash_3)

    hash_1.deep_merge!(hash_2)
    assert_equal expected, hash_1
  end

785 786 787 788 789 790 791 792 793 794
  def test_store_on_indifferent_access
    hash = HashWithIndifferentAccess.new
    hash.store(:test1, 1)
    hash.store('test1', 11)
    hash[:test2] = 2
    hash['test2'] = 22
    expected = { "test1" => 11, "test2" => 22 }
    assert_equal expected, hash
  end

795 796 797 798 799 800 801 802 803
  def test_constructor_on_indifferent_access
    hash = HashWithIndifferentAccess[:foo, 1]
    assert_equal 1, hash[:foo]
    assert_equal 1, hash['foo']
    hash[:foo] = 3
    assert_equal 3, hash[:foo]
    assert_equal 3, hash['foo']
  end

804
  def test_reverse_merge
805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
    defaults = { :a => "x", :b => "y", :c => 10 }.freeze
    options  = { :a => 1, :b => 2 }
    expected = { :a => 1, :b => 2, :c => 10 }

    # Should merge defaults into options, creating a new hash.
    assert_equal expected, options.reverse_merge(defaults)
    assert_not_equal expected, options

    # Should merge! defaults into options, replacing options.
    merged = options.dup
    assert_equal expected, merged.reverse_merge!(defaults)
    assert_equal expected, merged

    # Should be an alias for reverse_merge!
    merged = options.dup
    assert_equal expected, merged.reverse_update(defaults)
    assert_equal expected, merged
822
  end
823

824 825 826 827 828 829 830
  def test_slice
    original = { :a => 'x', :b => 'y', :c => 10 }
    expected = { :a => 'x', :b => 'y' }

    # Should return a new hash with only the given keys.
    assert_equal expected, original.slice(:a, :b)
    assert_not_equal expected, original
831 832 833 834 835
  end

  def test_slice_inplace
    original = { :a => 'x', :b => 'y', :c => 10 }
    expected = { :c => 10 }
836 837 838 839 840

    # Should replace the hash with only the given keys.
    assert_equal expected, original.slice!(:a, :b)
  end

841 842 843 844 845 846 847
  def test_slice_with_an_array_key
    original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" }
    expected = { [:a, :b] => "an array key", :c => 10 }

    # Should return a new hash with only the given keys when given an array key.
    assert_equal expected, original.slice([:a, :b], :c)
    assert_not_equal expected, original
848 849 850 851 852
  end

  def test_slice_inplace_with_an_array_key
    original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" }
    expected = { :a => 'x', :b => 'y' }
853 854 855 856 857 858 859 860 861 862 863 864 865

    # Should replace the hash with only the given keys when given an array key.
    assert_equal expected, original.slice!([:a, :b], :c)
  end

  def test_slice_with_splatted_keys
    original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" }
    expected = { :a => 'x', :b => "y" }

    # Should grab each of the splatted keys.
    assert_equal expected, original.slice(*[:a, :b])
  end

866 867 868 869 870 871 872 873
  def test_indifferent_slice
    original = { :a => 'x', :b => 'y', :c => 10 }.with_indifferent_access
    expected = { :a => 'x', :b => 'y' }.with_indifferent_access

    [['a', 'b'], [:a, :b]].each do |keys|
      # Should return a new hash with only the given keys.
      assert_equal expected, original.slice(*keys), keys.inspect
      assert_not_equal expected, original
874 875
    end
  end
876

877 878 879 880 881
  def test_indifferent_slice_inplace
    original = { :a => 'x', :b => 'y', :c => 10 }.with_indifferent_access
    expected = { :c => 10 }.with_indifferent_access

    [['a', 'b'], [:a, :b]].each do |keys|
882 883 884 885 886
      # Should replace the hash with only the given keys.
      copy = original.dup
      assert_equal expected, copy.slice!(*keys)
    end
  end
887

888 889 890 891 892 893 894 895 896 897
  def test_indifferent_slice_access_with_symbols
    original = {'login' => 'bender', 'password' => 'shiny', 'stuff' => 'foo'}
    original = original.with_indifferent_access

    slice = original.slice(:login, :password)

    assert_equal 'bender', slice[:login]
    assert_equal 'bender', slice['login']
  end

898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915
  def test_slice_bang_does_not_override_default
    hash = Hash.new(0)
    hash.update(a: 1, b: 2)

    hash.slice!(:a)

    assert_equal 0, hash[:c]
  end

  def test_slice_bang_does_not_override_default_proc
    hash = Hash.new { |h, k| h[k] = [] }
    hash.update(a: 1, b: 2)

    hash.slice!(:a)

    assert_equal [], hash[:c]
  end

916 917 918
  def test_extract
    original = {:a => 1, :b => 2, :c => 3, :d => 4}
    expected = {:a => 1, :b => 2}
919
    remaining = {:c => 3, :d => 4}
920

921
    assert_equal expected, original.extract!(:a, :b, :x)
922
    assert_equal remaining, original
923 924 925 926 927 928 929 930 931 932 933 934 935
  end

  def test_extract_nils
    original = {:a => nil, :b => nil}
    expected = {:a => nil}
    extracted = original.extract!(:a, :x)

    assert_equal expected, extracted
    assert_equal nil, extracted[:a]
    assert_equal nil, extracted[:x]
  end

  def test_indifferent_extract
936
    original = {:a => 1, 'b' => 2, :c => 3, 'd' => 4}.with_indifferent_access
937
    expected = {:a => 1, :b => 2}.with_indifferent_access
938
    remaining = {:c => 3, :d => 4}.with_indifferent_access
939

940 941 942 943 944
    [['a', 'b'], [:a, :b]].each do |keys|
      copy = original.dup
      assert_equal expected, copy.extract!(*keys)
      assert_equal remaining, copy
    end
945 946
  end

947 948 949 950
  def test_except
    original = { :a => 'x', :b => 'y', :c => 10 }
    expected = { :a => 'x', :b => 'y' }

E
Edward Tsech 已提交
951
    # Should return a new hash without the given keys.
952 953 954
    assert_equal expected, original.except(:c)
    assert_not_equal expected, original

E
Edward Tsech 已提交
955
    # Should replace the hash without the given keys.
956 957 958
    assert_equal expected, original.except!(:c)
    assert_equal expected, original
  end
959

960 961 962
  def test_except_with_more_than_one_argument
    original = { :a => 'x', :b => 'y', :c => 10 }
    expected = { :a => 'x' }
963

964
    assert_equal expected, original.except(:b, :c)
965 966 967

    assert_equal expected, original.except!(:b, :c)
    assert_equal expected, original
968 969
  end

970 971 972 973
  def test_except_with_original_frozen
    original = { :a => 'x', :b => 'y' }
    original.freeze
    assert_nothing_raised { original.except(:a) }
974 975

    assert_raise(RuntimeError) { original.except!(:a) }
976 977
  end

978
  def test_except_does_not_delete_values_in_original
979
    original = { :a => 'x', :b => 'y' }
980 981 982
    assert_not_called(original, :delete) do
      original.except(:a)
    end
983
  end
984 985 986 987

  def test_compact
    hash_contain_nil_value = @symbols.merge(z: nil)
    hash_with_only_nil_values = { a: nil, b: nil }
988

989 990 991
    h = hash_contain_nil_value.dup
    assert_equal(@symbols, h.compact)
    assert_equal(hash_contain_nil_value, h)
992

993 994 995 996 997 998 999 1000
    h = hash_with_only_nil_values.dup
    assert_equal({}, h.compact)
    assert_equal(hash_with_only_nil_values, h)
  end

  def test_compact!
    hash_contain_nil_value = @symbols.merge(z: nil)
    hash_with_only_nil_values = { a: nil, b: nil }
1001

1002 1003 1004
    h = hash_contain_nil_value.dup
    assert_equal(@symbols, h.compact!)
    assert_equal(@symbols, h)
1005

1006 1007 1008 1009
    h = hash_with_only_nil_values.dup
    assert_equal({}, h.compact!)
    assert_equal({}, h)
  end
1010 1011 1012 1013 1014 1015

  def test_new_with_to_hash_conversion
    hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1))
    assert hash.key?('a')
    assert_equal 1, hash[:a]
  end
1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032

  def test_dup_with_default_proc
    hash = HashWithIndifferentAccess.new
    hash.default_proc = proc { |h, v| raise "walrus" }
    assert_nothing_raised { hash.dup }
  end

  def test_dup_with_default_proc_sets_proc
    hash = HashWithIndifferentAccess.new
    hash.default_proc = proc { |h, k| k + 1 }
    new_hash = hash.dup

    assert_equal 3, new_hash[2]

    new_hash.default = 2
    assert_equal 2, new_hash[:non_existant]
  end
1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044

  def test_to_hash_with_raising_default_proc
    hash = HashWithIndifferentAccess.new
    hash.default_proc = proc { |h, k| raise "walrus" }

    assert_nothing_raised { hash.to_hash }
  end

  def test_new_from_hash_copying_default_should_not_raise_when_default_proc_does
    hash = Hash.new
    hash.default_proc = proc { |h, k| raise "walrus" }

1045
    assert_deprecated { HashWithIndifferentAccess.new_from_hash_copying_default(hash) }
1046
  end
1047

1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064
  def test_new_with_to_hash_conversion_copies_default
    normal_hash = Hash.new(3)
    normal_hash[:a] = 1

    hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
    assert_equal 1, hash[:a]
    assert_equal 3, hash[:b]
  end

  def test_new_with_to_hash_conversion_copies_default_proc
    normal_hash = Hash.new { 1 + 2 }
    normal_hash[:a] = 1

    hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
    assert_equal 1, hash[:a]
    assert_equal 3, hash[:b]
  end
1065
end
1066

1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077
class IWriteMyOwnXML
  def to_xml(options = {})
    options[:indent] ||= 2
    xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
    xml.instruct! unless options[:skip_instruct]
    xml.level_one do
      xml.tag!(:second_level, 'content')
    end
  end
end

1078
class HashExtToParamTests < ActiveSupport::TestCase
1079 1080 1081 1082 1083 1084 1085 1086 1087 1088
  class ToParam < String
    def to_param
      "#{self}-1"
    end
  end

  def test_string_hash
    assert_equal '', {}.to_param
    assert_equal 'hello=world', { :hello => "world" }.to_param
    assert_equal 'hello=10', { "hello" => 10 }.to_param
1089
    assert_equal 'hello=world&say_bye=true', {:hello => "world", "say_bye" => true}.to_param
1090 1091 1092
  end

  def test_number_hash
1093
    assert_equal '10=20&30=40&50=60', {10 => 20, 30 => 40, 50 => 60}.to_param
1094 1095 1096
  end

  def test_to_param_hash
1097
    assert_equal 'custom-1=param-1&custom2-1=param2-1', {ToParam.new('custom') => ToParam.new('param'), ToParam.new('custom2') => ToParam.new('param2')}.to_param
1098
  end
1099 1100 1101 1102

  def test_to_param_hash_escapes_its_keys_and_values
    assert_equal 'param+1=A+string+with+%2F+characters+%26+that+should+be+%3F+escaped', { 'param 1' => 'A string with / characters & that should be ? escaped' }.to_param
  end
1103

1104
  def test_to_param_orders_by_key_in_ascending_order
V
Vishnu Atrai 已提交
1105
    assert_equal 'a=2&b=1&c=0', Hash[*%w(b 1 c 0 a 2)].to_param
1106
  end
1107 1108
end

1109
class HashToXmlTest < ActiveSupport::TestCase
1110 1111 1112 1113
  def setup
    @xml_options = { :root => :person, :skip_instruct => true, :indent => 0 }
  end

1114
  def test_one_level
1115 1116 1117 1118
    xml = { :name => "David", :street => "Paulina" }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street>Paulina</street>))
    assert xml.include?(%(<name>David</name>))
1119 1120
  end

1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134
  def test_one_level_dasherize_false
    xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:dasherize => false))
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street_name>Paulina</street_name>))
    assert xml.include?(%(<name>David</name>))
  end

  def test_one_level_dasherize_true
    xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:dasherize => true))
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street-name>Paulina</street-name>))
    assert xml.include?(%(<name>David</name>))
  end

1135 1136 1137 1138 1139 1140 1141
  def test_one_level_camelize_true
    xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:camelize => true))
    assert_equal "<Person>", xml.first(8)
    assert xml.include?(%(<StreetName>Paulina</StreetName>))
    assert xml.include?(%(<Name>David</Name>))
  end

1142 1143 1144 1145 1146 1147 1148
  def test_one_level_camelize_lower
    xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:camelize => :lower))
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<streetName>Paulina</streetName>))
    assert xml.include?(%(<name>David</name>))
  end

1149
  def test_one_level_with_types
1150
    xml = { :name => "David", :street => "Paulina", :age => 26, :age_in_millis => 820497600000, :moved_on => Date.new(2005, 11, 15), :resident => :yes }.to_xml(@xml_options)
1151 1152 1153 1154
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street>Paulina</street>))
    assert xml.include?(%(<name>David</name>))
    assert xml.include?(%(<age type="integer">26</age>))
1155
    assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>))
1156
    assert xml.include?(%(<moved-on type="date">2005-11-15</moved-on>))
1157
    assert xml.include?(%(<resident type="symbol">yes</resident>))
1158 1159 1160
  end

  def test_one_level_with_nils
1161 1162 1163 1164
    xml = { :name => "David", :street => "Paulina", :age => nil }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street>Paulina</street>))
    assert xml.include?(%(<name>David</name>))
1165
    assert xml.include?(%(<age nil="true"/>))
1166 1167 1168 1169 1170 1171 1172
  end

  def test_one_level_with_skipping_types
    xml = { :name => "David", :street => "Paulina", :age => nil }.to_xml(@xml_options.merge(:skip_types => true))
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street>Paulina</street>))
    assert xml.include?(%(<name>David</name>))
1173
    assert xml.include?(%(<age nil="true"/>))
1174 1175
  end

1176
  def test_one_level_with_yielding
1177 1178
    xml = { :name => "David", :street => "Paulina" }.to_xml(@xml_options) do |x|
      x.creator("Rails")
1179 1180 1181 1182 1183 1184 1185 1186
    end

    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street>Paulina</street>))
    assert xml.include?(%(<name>David</name>))
    assert xml.include?(%(<creator>Rails</creator>))
  end

1187
  def test_two_levels
1188 1189 1190 1191
    xml = { :name => "David", :address => { :street => "Paulina" } }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<address><street>Paulina</street></address>))
    assert xml.include?(%(<name>David</name>))
1192
  end
1193 1194 1195 1196 1197 1198 1199

  def test_two_levels_with_second_level_overriding_to_xml
    xml = { :name => "David", :address => { :street => "Paulina" }, :child => IWriteMyOwnXML.new }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<address><street>Paulina</street></address>))
    assert xml.include?(%(<level_one><second_level>content</second_level></level_one>))
  end
1200

1201 1202 1203
  def test_two_levels_with_array
    xml = { :name => "David", :addresses => [{ :street => "Paulina" }, { :street => "Evergreen" }] }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
1204
    assert xml.include?(%(<addresses type="array"><address>))
1205 1206 1207 1208
    assert xml.include?(%(<address><street>Paulina</street></address>))
    assert xml.include?(%(<address><street>Evergreen</street></address>))
    assert xml.include?(%(<name>David</name>))
  end
1209

1210 1211
  def test_three_levels_with_array
    xml = { :name => "David", :addresses => [{ :streets => [ { :name => "Paulina" }, { :name => "Paulina" } ] } ] }.to_xml(@xml_options)
1212
    assert xml.include?(%(<addresses type="array"><address><streets type="array"><street><name>))
1213
  end
1214

1215 1216 1217 1218 1219
  def test_timezoned_attributes
    xml = {
      :created_at => Time.utc(1999,2,2),
      :local_created_at => Time.utc(1999,2,2).in_time_zone('Eastern Time (US & Canada)')
    }.to_xml(@xml_options)
1220 1221
    assert_match %r{<created-at type=\"dateTime\">1999-02-02T00:00:00Z</created-at>}, xml
    assert_match %r{<local-created-at type=\"dateTime\">1999-02-01T19:00:00-05:00</local-created-at>}, xml
1222 1223
  end

1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272
  def test_multiple_records_from_xml_with_attributes_other_than_type_ignores_them_without_exploding
    topics_xml = <<-EOT
      <topics type="array" page="1" page-count="1000" per-page="2">
        <topic>
          <title>The First Topic</title>
          <author-name>David</author-name>
          <id type="integer">1</id>
          <approved type="boolean">false</approved>
          <replies-count type="integer">0</replies-count>
          <replies-close-in type="integer">2592000000</replies-close-in>
          <written-on type="date">2003-07-16</written-on>
          <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
          <content>Have a nice day</content>
          <author-email-address>david@loudthinking.com</author-email-address>
          <parent-id nil="true"></parent-id>
        </topic>
        <topic>
          <title>The Second Topic</title>
          <author-name>Jason</author-name>
          <id type="integer">1</id>
          <approved type="boolean">false</approved>
          <replies-count type="integer">0</replies-count>
          <replies-close-in type="integer">2592000000</replies-close-in>
          <written-on type="date">2003-07-16</written-on>
          <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
          <content>Have a nice day</content>
          <author-email-address>david@loudthinking.com</author-email-address>
          <parent-id></parent-id>
        </topic>
      </topics>
    EOT

    expected_topic_hash = {
      :title => "The First Topic",
      :author_name => "David",
      :id => 1,
      :approved => false,
      :replies_count => 0,
      :replies_close_in => 2592000000,
      :written_on => Date.new(2003, 7, 16),
      :viewed_at => Time.utc(2003, 7, 16, 9, 28),
      :content => "Have a nice day",
      :author_email_address => "david@loudthinking.com",
      :parent_id => nil
    }.stringify_keys

    assert_equal expected_topic_hash, Hash.from_xml(topics_xml)["topics"].first
  end

1273 1274 1275 1276 1277 1278
  def test_single_record_from_xml
    topic_xml = <<-EOT
      <topic>
        <title>The First Topic</title>
        <author-name>David</author-name>
        <id type="integer">1</id>
1279
        <approved type="boolean"> true </approved>
1280
        <replies-count type="integer">0</replies-count>
1281
        <replies-close-in type="integer">2592000000</replies-close-in>
1282 1283 1284 1285
        <written-on type="date">2003-07-16</written-on>
        <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
        <author-email-address>david@loudthinking.com</author-email-address>
        <parent-id></parent-id>
1286 1287
        <ad-revenue type="decimal">1.5</ad-revenue>
        <optimum-viewing-angle type="float">135</optimum-viewing-angle>
1288 1289
      </topic>
    EOT
1290

1291 1292 1293 1294
    expected_topic_hash = {
      :title => "The First Topic",
      :author_name => "David",
      :id => 1,
1295
      :approved => true,
1296
      :replies_count => 0,
1297
      :replies_close_in => 2592000000,
1298 1299 1300
      :written_on => Date.new(2003, 7, 16),
      :viewed_at => Time.utc(2003, 7, 16, 9, 28),
      :author_email_address => "david@loudthinking.com",
1301 1302
      :parent_id => nil,
      :ad_revenue => BigDecimal("1.50"),
1303
      :optimum_viewing_angle => 135.0,
1304
    }.stringify_keys
1305

1306
    assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"]
1307 1308
  end

1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321
  def test_single_record_from_xml_with_nil_values
    topic_xml = <<-EOT
      <topic>
        <title></title>
        <id type="integer"></id>
        <approved type="boolean"></approved>
        <written-on type="date"></written-on>
        <viewed-at type="datetime"></viewed-at>
        <parent-id></parent-id>
      </topic>
    EOT

    expected_topic_hash = {
1322
      :title      => nil,
1323 1324 1325
      :id         => nil,
      :approved   => nil,
      :written_on => nil,
1326
      :viewed_at  => nil,
1327 1328 1329
      :parent_id  => nil
    }.stringify_keys

1330
    assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"]
1331 1332
  end

1333 1334
  def test_multiple_records_from_xml
    topics_xml = <<-EOT
1335
      <topics type="array">
1336 1337 1338 1339 1340 1341
        <topic>
          <title>The First Topic</title>
          <author-name>David</author-name>
          <id type="integer">1</id>
          <approved type="boolean">false</approved>
          <replies-count type="integer">0</replies-count>
1342
          <replies-close-in type="integer">2592000000</replies-close-in>
1343 1344 1345 1346
          <written-on type="date">2003-07-16</written-on>
          <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
          <content>Have a nice day</content>
          <author-email-address>david@loudthinking.com</author-email-address>
1347
          <parent-id nil="true"></parent-id>
1348 1349 1350 1351 1352 1353 1354
        </topic>
        <topic>
          <title>The Second Topic</title>
          <author-name>Jason</author-name>
          <id type="integer">1</id>
          <approved type="boolean">false</approved>
          <replies-count type="integer">0</replies-count>
1355
          <replies-close-in type="integer">2592000000</replies-close-in>
1356 1357 1358 1359 1360 1361 1362 1363
          <written-on type="date">2003-07-16</written-on>
          <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
          <content>Have a nice day</content>
          <author-email-address>david@loudthinking.com</author-email-address>
          <parent-id></parent-id>
        </topic>
      </topics>
    EOT
1364

1365 1366 1367 1368 1369 1370
    expected_topic_hash = {
      :title => "The First Topic",
      :author_name => "David",
      :id => 1,
      :approved => false,
      :replies_count => 0,
1371
      :replies_close_in => 2592000000,
1372 1373 1374 1375 1376 1377
      :written_on => Date.new(2003, 7, 16),
      :viewed_at => Time.utc(2003, 7, 16, 9, 28),
      :content => "Have a nice day",
      :author_email_address => "david@loudthinking.com",
      :parent_id => nil
    }.stringify_keys
1378

1379
    assert_equal expected_topic_hash, Hash.from_xml(topics_xml)["topics"].first
1380
  end
1381

1382 1383 1384
  def test_single_record_from_xml_with_attributes_other_than_type
    topic_xml = <<-EOT
    <rsp stat="ok">
1385 1386 1387
      <photos page="1" pages="1" perpage="100" total="16">
        <photo id="175756086" owner="55569174@N00" secret="0279bf37a1" server="76" title="Colored Pencil PhotoBooth Fun" ispublic="1" isfriend="0" isfamily="0"/>
      </photos>
1388 1389
    </rsp>
    EOT
1390

1391 1392 1393 1394 1395 1396 1397 1398 1399 1400
    expected_topic_hash = {
      :id => "175756086",
      :owner => "55569174@N00",
      :secret => "0279bf37a1",
      :server => "76",
      :title => "Colored Pencil PhotoBooth Fun",
      :ispublic => "1",
      :isfriend => "0",
      :isfamily => "0",
    }.stringify_keys
1401

1402
    assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["rsp"]["photos"]["photo"]
1403
  end
1404

1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420
  def test_all_caps_key_from_xml
    test_xml = <<-EOT
      <ABC3XYZ>
        <TEST>Lorem Ipsum</TEST>
      </ABC3XYZ>
    EOT

    expected_hash = {
      "ABC3XYZ" => {
        "TEST" => "Lorem Ipsum"
      }
    }

    assert_equal expected_hash, Hash.from_xml(test_xml)
  end

1421 1422 1423 1424 1425 1426 1427 1428 1429 1430
  def test_empty_array_from_xml
    blog_xml = <<-XML
      <blog>
        <posts type="array"></posts>
      </blog>
    XML
    expected_blog_hash = {"blog" => {"posts" => []}}
    assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  end

1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441
  def test_empty_array_with_whitespace_from_xml
    blog_xml = <<-XML
      <blog>
        <posts type="array">
        </posts>
      </blog>
    XML
    expected_blog_hash = {"blog" => {"posts" => []}}
    assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  end

1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465
  def test_array_with_one_entry_from_xml
    blog_xml = <<-XML
      <blog>
        <posts type="array">
          <post>a post</post>
        </posts>
      </blog>
    XML
    expected_blog_hash = {"blog" => {"posts" => ["a post"]}}
    assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  end

  def test_array_with_multiple_entries_from_xml
    blog_xml = <<-XML
      <blog>
        <posts type="array">
          <post>a post</post>
          <post>another post</post>
        </posts>
      </blog>
    XML
    expected_blog_hash = {"blog" => {"posts" => ["a post", "another post"]}}
    assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  end
1466

1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494
  def test_file_from_xml
    blog_xml = <<-XML
      <blog>
        <logo type="file" name="logo.png" content_type="image/png">
        </logo>
      </blog>
    XML
    hash = Hash.from_xml(blog_xml)
    assert hash.has_key?('blog')
    assert hash['blog'].has_key?('logo')

    file = hash['blog']['logo']
    assert_equal 'logo.png', file.original_filename
    assert_equal 'image/png', file.content_type
  end

  def test_file_from_xml_with_defaults
    blog_xml = <<-XML
      <blog>
        <logo type="file">
        </logo>
      </blog>
    XML
    file = Hash.from_xml(blog_xml)['blog']['logo']
    assert_equal 'untitled', file.original_filename
    assert_equal 'application/octet-stream', file.content_type
  end

1495 1496 1497 1498 1499 1500 1501 1502
  def test_tag_with_attrs_and_whitespace
    xml = <<-XML
      <blog name="bacon is the best">
      </blog>
    XML
    hash = Hash.from_xml(xml)
    assert_equal "bacon is the best", hash['blog']['name']
  end
1503

1504
  def test_empty_cdata_from_xml
1505
    xml = "<data><![CDATA[]]></data>"
1506

1507
    assert_equal "", Hash.from_xml(xml)["data"]
1508
  end
1509

1510 1511 1512 1513 1514 1515 1516 1517 1518
  def test_xsd_like_types_from_xml
    bacon_xml = <<-EOT
    <bacon>
      <weight type="double">0.5</weight>
      <price type="decimal">12.50</price>
      <chunky type="boolean"> 1 </chunky>
      <expires-at type="dateTime">2007-12-25T12:34:56+0000</expires-at>
      <notes type="string"></notes>
      <illustration type="base64Binary">YmFiZS5wbmc=</illustration>
1519
      <caption type="binary" encoding="base64">VGhhdCdsbCBkbywgcGlnLg==</caption>
1520 1521 1522 1523 1524 1525 1526 1527 1528
    </bacon>
    EOT

    expected_bacon_hash = {
      :weight => 0.5,
      :chunky => true,
      :price => BigDecimal("12.50"),
      :expires_at => Time.utc(2007,12,25,12,34,56),
      :notes => "",
1529 1530
      :illustration => "babe.png",
      :caption => "That'll do, pig."
1531 1532 1533 1534
    }.stringify_keys

    assert_equal expected_bacon_hash, Hash.from_xml(bacon_xml)["bacon"]
  end
1535

1536 1537 1538 1539 1540
  def test_type_trickles_through_when_unknown
    product_xml = <<-EOT
    <product>
      <weight type="double">0.5</weight>
      <image type="ProductImage"><filename>image.gif</filename></image>
1541

1542 1543 1544 1545 1546 1547 1548 1549
    </product>
    EOT

    expected_product_hash = {
      :weight => 0.5,
      :image => {'type' => 'ProductImage', 'filename' => 'image.gif' },
    }.stringify_keys

1550
    assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"]
1551
  end
1552

1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568
  def test_from_xml_raises_on_disallowed_type_attributes
    assert_raise ActiveSupport::XMLConverter::DisallowedType do
      Hash.from_xml '<product><name type="foo">value</name></product>', %w(foo)
    end
  end

  def test_from_xml_disallows_symbol_and_yaml_types_by_default
    assert_raise ActiveSupport::XMLConverter::DisallowedType do
      Hash.from_xml '<product><name type="symbol">value</name></product>'
    end

    assert_raise ActiveSupport::XMLConverter::DisallowedType do
      Hash.from_xml '<product><name type="yaml">value</name></product>'
    end
  end

1569 1570 1571 1572 1573 1574 1575 1576 1577 1578
  def test_from_xml_array_one
    expected = { 'numbers' => { 'type' => 'Array', 'value' => '1' }}
    assert_equal expected, Hash.from_xml('<numbers type="Array"><value>1</value></numbers>')
  end

  def test_from_xml_array_many
    expected = { 'numbers' => { 'type' => 'Array', 'value' => [ '1', '2' ] }}
    assert_equal expected, Hash.from_xml('<numbers type="Array"><value>1</value><value>2</value></numbers>')
  end

1579 1580 1581 1582 1583 1584
  def test_from_trusted_xml_allows_symbol_and_yaml_types
    expected = { 'product' => { 'name' => :value }}
    assert_equal expected, Hash.from_trusted_xml('<product><name type="symbol">value</name></product>')
    assert_equal expected, Hash.from_trusted_xml('<product><name type="yaml">:value</name></product>')
  end

1585 1586 1587 1588 1589
  def test_should_use_default_proc_for_unknown_key
    hash_wia = HashWithIndifferentAccess.new { 1 +  2 }
    assert_equal 3, hash_wia[:new_key]
  end

1590
  def test_should_return_nil_if_no_key_is_supplied
1591
    hash_wia = HashWithIndifferentAccess.new { 1 +  2 }
1592
    assert_equal nil, hash_wia.default
1593 1594
  end

1595 1596 1597 1598
  def test_should_use_default_value_for_unknown_key
    hash_wia = HashWithIndifferentAccess.new(3)
    assert_equal 3, hash_wia[:new_key]
  end
1599

1600 1601 1602 1603
  def test_should_use_default_value_if_no_key_is_supplied
    hash_wia = HashWithIndifferentAccess.new(3)
    assert_equal 3, hash_wia.default
  end
1604

1605 1606 1607 1608
  def test_should_nil_if_no_default_value_is_supplied
    hash_wia = HashWithIndifferentAccess.new
    assert_nil hash_wia.default
  end
1609

1610
  def test_should_return_dup_for_with_indifferent_access
1611 1612
    hash_wia = HashWithIndifferentAccess.new
    assert_equal hash_wia, hash_wia.with_indifferent_access
1613
    assert_not_same hash_wia, hash_wia.with_indifferent_access
1614 1615
  end

1616 1617 1618 1619 1620 1621 1622
  def test_allows_setting_frozen_array_values_with_indifferent_access
    value = [1, 2, 3].freeze
    hash = HashWithIndifferentAccess.new
    hash[:key] = value
    assert_equal hash[:key], value
  end

1623 1624 1625 1626 1627
  def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
    hash = Hash.new(3)
    hash_wia = hash.with_indifferent_access
    assert_equal 3, hash_wia.default
  end
1628

1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639
  def test_should_copy_the_default_proc_when_converting_to_hash_with_indifferent_access
    hash = Hash.new do
      2 + 1
    end
    assert_equal 3, hash[:foo]

    hash_wia = hash.with_indifferent_access
    assert_equal 3, hash_wia[:foo]
    assert_equal 3, hash_wia[:bar]
  end

1640 1641 1642 1643 1644 1645 1646 1647 1648 1649
  # The XML builder seems to fail miserably when trying to tag something
  # with the same name as a Kernel method (throw, test, loop, select ...)
  def test_kernel_method_names_to_xml
    hash     = { :throw => { :ball => 'red' } }
    expected = '<person><throw><ball>red</ball></throw></person>'

    assert_nothing_raised do
      assert_equal expected, hash.to_xml(@xml_options)
    end
  end
1650 1651

  def test_empty_string_works_for_typecast_xml_value
1652
    assert_nothing_raised do
1653
      ActiveSupport::XMLConverter.new("").to_h
1654 1655
    end
  end
1656

1657
  def test_escaping_to_xml
1658 1659
    hash = {
      :bare_string        => 'First & Last Name',
1660 1661
      :pre_escaped_string => 'First &amp; Last Name'
    }.stringify_keys
1662

1663 1664 1665
    expected_xml = '<person><bare-string>First &amp; Last Name</bare-string><pre-escaped-string>First &amp;amp; Last Name</pre-escaped-string></person>'
    assert_equal expected_xml, hash.to_xml(@xml_options)
  end
1666

1667 1668
  def test_unescaping_from_xml
    xml_string = '<person><bare-string>First &amp; Last Name</bare-string><pre-escaped-string>First &amp;amp; Last Name</pre-escaped-string></person>'
1669 1670
    expected_hash = {
      :bare_string        => 'First & Last Name',
1671 1672 1673 1674
      :pre_escaped_string => 'First &amp; Last Name'
    }.stringify_keys
    assert_equal expected_hash, Hash.from_xml(xml_string)['person']
  end
1675

1676
  def test_roundtrip_to_xml_from_xml
1677 1678
    hash = {
      :bare_string        => 'First & Last Name',
1679 1680 1681 1682 1683
      :pre_escaped_string => 'First &amp; Last Name'
    }.stringify_keys

    assert_equal hash, Hash.from_xml(hash.to_xml(@xml_options))['person']
  end
1684

1685 1686 1687 1688 1689 1690 1691 1692 1693 1694
  def test_datetime_xml_type_with_utc_time
    alert_xml = <<-XML
      <alert>
        <alert_at type="datetime">2008-02-10T15:30:45Z</alert_at>
      </alert>
    XML
    alert_at = Hash.from_xml(alert_xml)['alert']['alert_at']
    assert alert_at.utc?
    assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
  end
1695

1696 1697 1698 1699 1700 1701 1702 1703 1704 1705
  def test_datetime_xml_type_with_non_utc_time
    alert_xml = <<-XML
      <alert>
        <alert_at type="datetime">2008-02-10T10:30:45-05:00</alert_at>
      </alert>
    XML
    alert_at = Hash.from_xml(alert_xml)['alert']['alert_at']
    assert alert_at.utc?
    assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
  end
1706

1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721
  def test_datetime_xml_type_with_far_future_date
    alert_xml = <<-XML
      <alert>
        <alert_at type="datetime">2050-02-10T15:30:45Z</alert_at>
      </alert>
    XML
    alert_at = Hash.from_xml(alert_xml)['alert']['alert_at']
    assert alert_at.utc?
    assert_equal 2050,  alert_at.year
    assert_equal 2,     alert_at.month
    assert_equal 10,    alert_at.day
    assert_equal 15,    alert_at.hour
    assert_equal 30,    alert_at.min
    assert_equal 45,    alert_at.sec
  end
1722 1723 1724 1725 1726 1727 1728

  def test_to_xml_dups_options
    options = {:skip_instruct => true}
    {}.to_xml(options)
    # :builder, etc, shouldn't be added to options
    assert_equal({:skip_instruct => true}, options)
  end
1729

1730
  def test_expansion_count_is_limited
J
Jeremy Kemper 已提交
1731 1732 1733 1734 1735 1736 1737 1738
    expected =
      case ActiveSupport::XmlMini.backend.name
      when 'ActiveSupport::XmlMini_REXML';        RuntimeError
      when 'ActiveSupport::XmlMini_Nokogiri';     Nokogiri::XML::SyntaxError
      when 'ActiveSupport::XmlMini_NokogiriSAX';  RuntimeError
      when 'ActiveSupport::XmlMini_LibXML';       LibXML::XML::Error
      when 'ActiveSupport::XmlMini_LibXMLSAX';    LibXML::XML::Error
      end
1739

1740
    assert_raise expected do
1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758
      attack_xml = <<-EOT
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE member [
        <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
        <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
        <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
        <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
        <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;">
        <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;">
        <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
      ]>
      <member>
      &a;
      </member>
      EOT
      Hash.from_xml(attack_xml)
    end
  end
J
Jeremy Kemper 已提交
1759
end