PageRenderTime 45ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/activesupport/test/multibyte_chars_test.rb

https://bitbucket.org/nicksieger/rails
Ruby | 577 lines | 487 code | 81 blank | 9 comment | 9 complexity | 3c14348766c933a43cf8f6354803eb20 MD5 | raw file
  1. # encoding: utf-8
  2. require 'abstract_unit'
  3. require 'multibyte_test_helpers'
  4. class String
  5. def __method_for_multibyte_testing_with_integer_result; 1; end
  6. def __method_for_multibyte_testing; 'result'; end
  7. def __method_for_multibyte_testing!; 'result'; end
  8. end
  9. class MultibyteCharsTest < Test::Unit::TestCase
  10. include MultibyteTestHelpers
  11. def setup
  12. @proxy_class = ActiveSupport::Multibyte::Chars
  13. @chars = @proxy_class.new UNICODE_STRING
  14. end
  15. def test_wraps_the_original_string
  16. assert_equal UNICODE_STRING, @chars.to_s
  17. assert_equal UNICODE_STRING, @chars.wrapped_string
  18. end
  19. def test_should_allow_method_calls_to_string
  20. assert_nothing_raised do
  21. @chars.__method_for_multibyte_testing
  22. end
  23. assert_raise NoMethodError do
  24. @chars.__unknown_method
  25. end
  26. end
  27. def test_forwarded_method_calls_should_return_new_chars_instance
  28. assert @chars.__method_for_multibyte_testing.kind_of?(@proxy_class)
  29. assert_not_equal @chars.object_id, @chars.__method_for_multibyte_testing.object_id
  30. end
  31. def test_forwarded_bang_method_calls_should_return_the_original_chars_instance
  32. assert @chars.__method_for_multibyte_testing!.kind_of?(@proxy_class)
  33. assert_equal @chars.object_id, @chars.__method_for_multibyte_testing!.object_id
  34. end
  35. def test_methods_are_forwarded_to_wrapped_string_for_byte_strings
  36. assert_equal BYTE_STRING.class, BYTE_STRING.mb_chars.class
  37. end
  38. def test_forwarded_method_with_non_string_result_should_be_returned_vertabim
  39. assert_equal ''.__method_for_multibyte_testing_with_integer_result, @chars.__method_for_multibyte_testing_with_integer_result
  40. end
  41. def test_should_concatenate
  42. assert_equal 'ab', 'a'.mb_chars + 'b'
  43. assert_equal 'ab', 'a' + 'b'.mb_chars
  44. assert_equal 'ab', 'a'.mb_chars + 'b'.mb_chars
  45. assert_equal 'ab', 'a'.mb_chars << 'b'
  46. assert_equal 'ab', 'a' << 'b'.mb_chars
  47. assert_equal 'ab', 'a'.mb_chars << 'b'.mb_chars
  48. end
  49. def test_consumes_utf8_strings
  50. assert @proxy_class.consumes?(UNICODE_STRING)
  51. assert @proxy_class.consumes?(ASCII_STRING)
  52. assert !@proxy_class.consumes?(BYTE_STRING)
  53. end
  54. def test_unpack_utf8_strings
  55. assert_equal 4, @proxy_class.u_unpack(UNICODE_STRING).length
  56. assert_equal 5, @proxy_class.u_unpack(ASCII_STRING).length
  57. end
  58. def test_unpack_raises_encoding_error_on_broken_strings
  59. assert_raise(ActiveSupport::Multibyte::EncodingError) do
  60. @proxy_class.u_unpack(BYTE_STRING)
  61. end
  62. end
  63. if RUBY_VERSION < '1.9'
  64. def test_concatenation_should_return_a_proxy_class_instance
  65. assert_equal ActiveSupport::Multibyte.proxy_class, ('a'.mb_chars + 'b').class
  66. assert_equal ActiveSupport::Multibyte.proxy_class, ('a'.mb_chars << 'b').class
  67. end
  68. def test_ascii_strings_are_treated_at_utf8_strings
  69. assert_equal ActiveSupport::Multibyte.proxy_class, ASCII_STRING.mb_chars.class
  70. end
  71. def test_concatenate_should_return_proxy_instance
  72. assert(('a'.mb_chars + 'b').kind_of?(@proxy_class))
  73. assert(('a'.mb_chars + 'b'.mb_chars).kind_of?(@proxy_class))
  74. assert(('a'.mb_chars << 'b').kind_of?(@proxy_class))
  75. assert(('a'.mb_chars << 'b'.mb_chars).kind_of?(@proxy_class))
  76. end
  77. end
  78. end
  79. class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
  80. include MultibyteTestHelpers
  81. def setup
  82. @chars = UNICODE_STRING.dup.mb_chars
  83. # NEWLINE, SPACE, EM SPACE
  84. @whitespace = "\n#{[32, 8195].pack('U*')}"
  85. @whitespace.force_encoding(Encoding::UTF_8) if @whitespace.respond_to?(:force_encoding)
  86. @byte_order_mark = [65279].pack('U')
  87. end
  88. if RUBY_VERSION < '1.9'
  89. def test_split_should_return_an_array_of_chars_instances
  90. @chars.split(//).each do |character|
  91. assert character.kind_of?(ActiveSupport::Multibyte.proxy_class)
  92. end
  93. end
  94. def test_indexed_insert_accepts_fixnums
  95. @chars[2] = 32
  96. assert_equal 'こに わ', @chars
  97. end
  98. def test_overridden_bang_methods_return_self
  99. [:rstrip!, :lstrip!, :strip!, :reverse!, :upcase!, :downcase!, :capitalize!].each do |method|
  100. assert_equal @chars.object_id, @chars.send(method).object_id
  101. end
  102. end
  103. def test_overridden_bang_methods_change_wrapped_string
  104. [:rstrip!, :lstrip!, :strip!, :reverse!, :upcase!, :downcase!].each do |method|
  105. original = ' Café '
  106. proxy = chars(original.dup)
  107. proxy.send(method)
  108. assert_not_equal original, proxy.to_s
  109. end
  110. proxy = chars('òu')
  111. proxy.capitalize!
  112. assert_equal 'Òu', proxy.to_s
  113. end
  114. end
  115. if RUBY_VERSION >= '1.9'
  116. def test_unicode_string_should_have_utf8_encoding
  117. assert_equal Encoding::UTF_8, UNICODE_STRING.encoding
  118. end
  119. end
  120. def test_identity
  121. assert_equal @chars, @chars
  122. assert @chars.eql?(@chars)
  123. if RUBY_VERSION <= '1.9'
  124. assert !@chars.eql?(UNICODE_STRING)
  125. else
  126. assert @chars.eql?(UNICODE_STRING)
  127. end
  128. end
  129. def test_string_methods_are_chainable
  130. assert chars('').insert(0, '').kind_of?(ActiveSupport::Multibyte.proxy_class)
  131. assert chars('').rjust(1).kind_of?(ActiveSupport::Multibyte.proxy_class)
  132. assert chars('').ljust(1).kind_of?(ActiveSupport::Multibyte.proxy_class)
  133. assert chars('').center(1).kind_of?(ActiveSupport::Multibyte.proxy_class)
  134. assert chars('').rstrip.kind_of?(ActiveSupport::Multibyte.proxy_class)
  135. assert chars('').lstrip.kind_of?(ActiveSupport::Multibyte.proxy_class)
  136. assert chars('').strip.kind_of?(ActiveSupport::Multibyte.proxy_class)
  137. assert chars('').reverse.kind_of?(ActiveSupport::Multibyte.proxy_class)
  138. assert chars(' ').slice(0).kind_of?(ActiveSupport::Multibyte.proxy_class)
  139. assert chars('').upcase.kind_of?(ActiveSupport::Multibyte.proxy_class)
  140. assert chars('').downcase.kind_of?(ActiveSupport::Multibyte.proxy_class)
  141. assert chars('').capitalize.kind_of?(ActiveSupport::Multibyte.proxy_class)
  142. assert chars('').normalize.kind_of?(ActiveSupport::Multibyte.proxy_class)
  143. assert chars('').decompose.kind_of?(ActiveSupport::Multibyte.proxy_class)
  144. assert chars('').compose.kind_of?(ActiveSupport::Multibyte.proxy_class)
  145. assert chars('').tidy_bytes.kind_of?(ActiveSupport::Multibyte.proxy_class)
  146. end
  147. def test_should_be_equal_to_the_wrapped_string
  148. assert_equal UNICODE_STRING, @chars
  149. assert_equal @chars, UNICODE_STRING
  150. end
  151. def test_should_not_be_equal_to_an_other_string
  152. assert_not_equal @chars, 'other'
  153. assert_not_equal 'other', @chars
  154. end
  155. def test_sortability
  156. words = %w(builder armor zebra).map(&:mb_chars).sort
  157. assert_equal %w(armor builder zebra), words
  158. end
  159. def test_should_return_character_offset_for_regexp_matches
  160. assert_nil(@chars =~ /wrong/u)
  161. assert_equal 0, (@chars =~ //u)
  162. assert_equal 1, (@chars =~ //u)
  163. assert_equal 3, (@chars =~ //u)
  164. end
  165. def test_should_use_character_offsets_for_insert_offsets
  166. assert_equal '', ''.mb_chars.insert(0, '')
  167. assert_equal 'こわにちわ', @chars.insert(1, 'わ')
  168. assert_equal 'こわわわにちわ', @chars.insert(2, 'わわ')
  169. assert_equal 'わこわわわにちわ', @chars.insert(0, 'わ')
  170. assert_equal 'わこわわわにちわ', @chars.wrapped_string if RUBY_VERSION < '1.9'
  171. end
  172. def test_insert_should_be_destructive
  173. @chars.insert(1, 'わ')
  174. assert_equal 'こわにちわ', @chars
  175. end
  176. def test_insert_throws_index_error
  177. assert_raise(IndexError) { @chars.insert(-12, 'わ')}
  178. assert_raise(IndexError) { @chars.insert(12, 'わ') }
  179. end
  180. def test_should_know_if_one_includes_the_other
  181. assert @chars.include?('')
  182. assert @chars.include?('ち')
  183. assert @chars.include?('わ')
  184. assert !@chars.include?('こちわ')
  185. assert !@chars.include?('a')
  186. end
  187. def test_include_raises_type_error_when_nil_is_passed
  188. assert_raise(TypeError) do
  189. @chars.include?(nil)
  190. end
  191. end
  192. def test_index_should_return_character_offset
  193. assert_nil @chars.index('u')
  194. assert_equal 0, @chars.index('こに')
  195. assert_equal 2, @chars.index('ち')
  196. assert_equal 3, @chars.index('わ')
  197. end
  198. def test_indexed_insert_should_take_character_offsets
  199. @chars[2] = 'a'
  200. assert_equal 'こにaわ', @chars
  201. @chars[2] = 'ηη'
  202. assert_equal 'こにηηわ', @chars
  203. @chars[3, 2] = 'λλλ'
  204. assert_equal 'こにηλλλ', @chars
  205. @chars[1, 0] = "λ"
  206. assert_equal 'こλにηλλλ', @chars
  207. @chars[4..6] = "ηη"
  208. assert_equal 'こλにηηη', @chars
  209. @chars[/ηη/] = "λλλ"
  210. assert_equal 'こλにλλλη', @chars
  211. @chars[/(λλ)(.)/, 2] = "α"
  212. assert_equal 'こλにλλαη', @chars
  213. @chars["α"] = "¢"
  214. assert_equal 'こλにλλ¢η', @chars
  215. @chars["λλ"] = "ααα"
  216. assert_equal 'こλにααα¢η', @chars
  217. end
  218. def test_indexed_insert_should_raise_on_index_overflow
  219. before = @chars.to_s
  220. assert_raise(IndexError) { @chars[10] = 'a' }
  221. assert_raise(IndexError) { @chars[10, 4] = 'a' }
  222. assert_raise(IndexError) { @chars[/ii/] = 'a' }
  223. assert_raise(IndexError) { @chars[/()/, 10] = 'a' }
  224. assert_equal before, @chars
  225. end
  226. def test_indexed_insert_should_raise_on_range_overflow
  227. before = @chars.to_s
  228. assert_raise(RangeError) { @chars[10..12] = 'a' }
  229. assert_equal before, @chars
  230. end
  231. def test_rjust_should_raise_argument_errors_on_bad_arguments
  232. assert_raise(ArgumentError) { @chars.rjust(10, '') }
  233. assert_raise(ArgumentError) { @chars.rjust }
  234. end
  235. def test_rjust_should_count_characters_instead_of_bytes
  236. assert_equal UNICODE_STRING, @chars.rjust(-3)
  237. assert_equal UNICODE_STRING, @chars.rjust(0)
  238. assert_equal UNICODE_STRING, @chars.rjust(4)
  239. assert_equal " #{UNICODE_STRING}", @chars.rjust(5)
  240. assert_equal " #{UNICODE_STRING}", @chars.rjust(7)
  241. assert_equal "---#{UNICODE_STRING}", @chars.rjust(7, '-')
  242. assert_equal "ααα#{UNICODE_STRING}", @chars.rjust(7, 'α')
  243. assert_equal "aba#{UNICODE_STRING}", @chars.rjust(7, 'ab')
  244. assert_equal "αηα#{UNICODE_STRING}", @chars.rjust(7, 'αη')
  245. assert_equal "αηαη#{UNICODE_STRING}", @chars.rjust(8, 'αη')
  246. end
  247. def test_ljust_should_raise_argument_errors_on_bad_arguments
  248. assert_raise(ArgumentError) { @chars.ljust(10, '') }
  249. assert_raise(ArgumentError) { @chars.ljust }
  250. end
  251. def test_ljust_should_count_characters_instead_of_bytes
  252. assert_equal UNICODE_STRING, @chars.ljust(-3)
  253. assert_equal UNICODE_STRING, @chars.ljust(0)
  254. assert_equal UNICODE_STRING, @chars.ljust(4)
  255. assert_equal "#{UNICODE_STRING} ", @chars.ljust(5)
  256. assert_equal "#{UNICODE_STRING} ", @chars.ljust(7)
  257. assert_equal "#{UNICODE_STRING}---", @chars.ljust(7, '-')
  258. assert_equal "#{UNICODE_STRING}ααα", @chars.ljust(7, 'α')
  259. assert_equal "#{UNICODE_STRING}aba", @chars.ljust(7, 'ab')
  260. assert_equal "#{UNICODE_STRING}αηα", @chars.ljust(7, 'αη')
  261. assert_equal "#{UNICODE_STRING}αηαη", @chars.ljust(8, 'αη')
  262. end
  263. def test_center_should_raise_argument_errors_on_bad_arguments
  264. assert_raise(ArgumentError) { @chars.center(10, '') }
  265. assert_raise(ArgumentError) { @chars.center }
  266. end
  267. def test_center_should_count_charactes_instead_of_bytes
  268. assert_equal UNICODE_STRING, @chars.center(-3)
  269. assert_equal UNICODE_STRING, @chars.center(0)
  270. assert_equal UNICODE_STRING, @chars.center(4)
  271. assert_equal "#{UNICODE_STRING} ", @chars.center(5)
  272. assert_equal " #{UNICODE_STRING} ", @chars.center(6)
  273. assert_equal " #{UNICODE_STRING} ", @chars.center(7)
  274. assert_equal "--#{UNICODE_STRING}--", @chars.center(8, '-')
  275. assert_equal "--#{UNICODE_STRING}---", @chars.center(9, '-')
  276. assert_equal "αα#{UNICODE_STRING}αα", @chars.center(8, 'α')
  277. assert_equal "αα#{UNICODE_STRING}ααα", @chars.center(9, 'α')
  278. assert_equal "a#{UNICODE_STRING}ab", @chars.center(7, 'ab')
  279. assert_equal "ab#{UNICODE_STRING}ab", @chars.center(8, 'ab')
  280. assert_equal "abab#{UNICODE_STRING}abab", @chars.center(12, 'ab')
  281. assert_equal "α#{UNICODE_STRING}αη", @chars.center(7, 'αη')
  282. assert_equal "αη#{UNICODE_STRING}αη", @chars.center(8, 'αη')
  283. end
  284. def test_lstrip_strips_whitespace_from_the_left_of_the_string
  285. assert_equal UNICODE_STRING, UNICODE_STRING.mb_chars.lstrip
  286. assert_equal UNICODE_STRING, (@whitespace + UNICODE_STRING).mb_chars.lstrip
  287. assert_equal UNICODE_STRING + @whitespace, (@whitespace + UNICODE_STRING + @whitespace).mb_chars.lstrip
  288. end
  289. def test_rstrip_strips_whitespace_from_the_right_of_the_string
  290. assert_equal UNICODE_STRING, UNICODE_STRING.mb_chars.rstrip
  291. assert_equal UNICODE_STRING, (UNICODE_STRING + @whitespace).mb_chars.rstrip
  292. assert_equal @whitespace + UNICODE_STRING, (@whitespace + UNICODE_STRING + @whitespace).mb_chars.rstrip
  293. end
  294. def test_strip_strips_whitespace
  295. assert_equal UNICODE_STRING, UNICODE_STRING.mb_chars.strip
  296. assert_equal UNICODE_STRING, (@whitespace + UNICODE_STRING).mb_chars.strip
  297. assert_equal UNICODE_STRING, (UNICODE_STRING + @whitespace).mb_chars.strip
  298. assert_equal UNICODE_STRING, (@whitespace + UNICODE_STRING + @whitespace).mb_chars.strip
  299. end
  300. def test_stripping_whitespace_leaves_whitespace_within_the_string_intact
  301. string_with_whitespace = UNICODE_STRING + @whitespace + UNICODE_STRING
  302. assert_equal string_with_whitespace, string_with_whitespace.mb_chars.strip
  303. assert_equal string_with_whitespace, string_with_whitespace.mb_chars.lstrip
  304. assert_equal string_with_whitespace, string_with_whitespace.mb_chars.rstrip
  305. end
  306. def test_size_returns_characters_instead_of_bytes
  307. assert_equal 0, ''.mb_chars.size
  308. assert_equal 4, @chars.size
  309. assert_equal 4, @chars.length
  310. assert_equal 5, ASCII_STRING.mb_chars.size
  311. end
  312. def test_reverse_reverses_characters
  313. assert_equal '', ''.mb_chars.reverse
  314. assert_equal 'わちにこ', @chars.reverse
  315. end
  316. def test_slice_should_take_character_offsets
  317. assert_equal nil, ''.mb_chars.slice(0)
  318. assert_equal 'こ', @chars.slice(0)
  319. assert_equal 'わ', @chars.slice(3)
  320. assert_equal nil, ''.mb_chars.slice(-1..1)
  321. assert_equal '', ''.mb_chars.slice(0..10)
  322. assert_equal 'にちわ', @chars.slice(1..3)
  323. assert_equal 'にちわ', @chars.slice(1, 3)
  324. assert_equal 'こ', @chars.slice(0, 1)
  325. assert_equal 'ちわ', @chars.slice(2..10)
  326. assert_equal '', @chars.slice(4..10)
  327. assert_equal 'に', @chars.slice(//u)
  328. assert_equal 'にち', @chars.slice(/\w/u)
  329. assert_equal nil, @chars.slice(/unknown/u)
  330. assert_equal 'にち', @chars.slice(/(にち)/u, 1)
  331. assert_equal nil, @chars.slice(/(にち)/u, 2)
  332. assert_equal nil, @chars.slice(7..6)
  333. end
  334. def test_slice_bang_returns_sliced_out_substring
  335. assert_equal 'にち', @chars.slice!(1..2)
  336. end
  337. def test_slice_bang_removes_the_slice_from_the_receiver
  338. @chars.slice!(1..2)
  339. assert_equal 'こわ', @chars
  340. end
  341. def test_slice_should_throw_exceptions_on_invalid_arguments
  342. assert_raise(TypeError) { @chars.slice(2..3, 1) }
  343. assert_raise(TypeError) { @chars.slice(1, 2..3) }
  344. assert_raise(ArgumentError) { @chars.slice(1, 1, 1) }
  345. end
  346. def test_ord_should_return_unicode_value_for_first_character
  347. assert_equal 12371, @chars.ord
  348. end
  349. def test_upcase_should_upcase_ascii_characters
  350. assert_equal '', ''.mb_chars.upcase
  351. assert_equal 'ABC', 'aBc'.mb_chars.upcase
  352. end
  353. def test_downcase_should_downcase_ascii_characters
  354. assert_equal '', ''.mb_chars.downcase
  355. assert_equal 'abc', 'aBc'.mb_chars.downcase
  356. end
  357. def test_capitalize_should_work_on_ascii_characters
  358. assert_equal '', ''.mb_chars.capitalize
  359. assert_equal 'Abc', 'abc'.mb_chars.capitalize
  360. end
  361. def test_respond_to_knows_which_methods_the_proxy_responds_to
  362. assert ''.mb_chars.respond_to?(:slice) # Defined on Chars
  363. assert ''.mb_chars.respond_to?(:capitalize!) # Defined on Chars
  364. assert ''.mb_chars.respond_to?(:gsub) # Defined on String
  365. assert !''.mb_chars.respond_to?(:undefined_method) # Not defined
  366. end
  367. def test_acts_like_string
  368. assert 'Bambi'.mb_chars.acts_like_string?
  369. end
  370. end
  371. # The default Multibyte Chars proxy has more features than the normal string implementation. Tests
  372. # for the implementation of these features should run on all Ruby versions and shouldn't be tested
  373. # through the proxy methods.
  374. class MultibyteCharsExtrasTest < Test::Unit::TestCase
  375. include MultibyteTestHelpers
  376. if RUBY_VERSION >= '1.9'
  377. def test_tidy_bytes_is_broken_on_1_9_0
  378. assert_raise(ArgumentError) do
  379. assert_equal_codepoints [0xfffd].pack('U'), chars("\xef\xbf\xbd").tidy_bytes
  380. end
  381. end
  382. end
  383. def test_upcase_should_be_unicode_aware
  384. assert_equal "АБВГД\0F", chars("аБвгд\0f").upcase
  385. assert_equal 'こにちわ', chars('こにちわ').upcase
  386. end
  387. def test_downcase_should_be_unicode_aware
  388. assert_equal "абвгд\0f", chars("аБвгд\0f").downcase
  389. assert_equal 'こにちわ', chars('こにちわ').downcase
  390. end
  391. def test_capitalize_should_be_unicode_aware
  392. { 'аБвг аБвг' => 'Абвг абвг',
  393. 'аБвг АБВГ' => 'Абвг абвг',
  394. 'АБВГ АБВГ' => 'Абвг абвг',
  395. '' => '' }.each do |f,t|
  396. assert_equal t, chars(f).capitalize
  397. end
  398. end
  399. def test_composition_exclusion_is_set_up_properly
  400. # Normalization of DEVANAGARI LETTER QA breaks when composition exclusion isn't used correctly
  401. qa = [0x915, 0x93c].pack('U*')
  402. assert_equal qa, chars(qa).normalize(:c)
  403. end
  404. # Test for the Public Review Issue #29, bad explanation of composition might lead to a
  405. # bad implementation: http://www.unicode.org/review/pr-29.html
  406. def test_normalization_C_pri_29
  407. [
  408. [0x0B47, 0x0300, 0x0B3E],
  409. [0x1100, 0x0300, 0x1161]
  410. ].map { |c| c.pack('U*') }.each do |c|
  411. assert_equal_codepoints c, chars(c).normalize(:c)
  412. end
  413. end
  414. def test_normalization_shouldnt_strip_null_bytes
  415. null_byte_str = "Test\0test"
  416. assert_equal null_byte_str, chars(null_byte_str).normalize(:kc)
  417. assert_equal null_byte_str, chars(null_byte_str).normalize(:c)
  418. assert_equal null_byte_str, chars(null_byte_str).normalize(:d)
  419. assert_equal null_byte_str, chars(null_byte_str).normalize(:kd)
  420. assert_equal null_byte_str, chars(null_byte_str).decompose
  421. assert_equal null_byte_str, chars(null_byte_str).compose
  422. end
  423. def test_simple_normalization
  424. comp_str = [
  425. 44, # LATIN CAPITAL LETTER D
  426. 307, # COMBINING DOT ABOVE
  427. 328, # COMBINING OGONEK
  428. 323 # COMBINING DOT BELOW
  429. ].pack("U*")
  430. assert_equal_codepoints '', chars('').normalize
  431. assert_equal_codepoints [44,105,106,328,323].pack("U*"), chars(comp_str).normalize(:kc).to_s
  432. assert_equal_codepoints [44,307,328,323].pack("U*"), chars(comp_str).normalize(:c).to_s
  433. assert_equal_codepoints [44,307,110,780,78,769].pack("U*"), chars(comp_str).normalize(:d).to_s
  434. assert_equal_codepoints [44,105,106,110,780,78,769].pack("U*"), chars(comp_str).normalize(:kd).to_s
  435. end
  436. def test_should_compute_grapheme_length
  437. [
  438. ['', 0],
  439. ['abc', 3],
  440. ['こにちわ', 4],
  441. [[0x0924, 0x094D, 0x0930].pack('U*'), 2],
  442. [%w(cr lf), 1],
  443. [%w(l l), 1],
  444. [%w(l v), 1],
  445. [%w(l lv), 1],
  446. [%w(l lvt), 1],
  447. [%w(lv v), 1],
  448. [%w(lv t), 1],
  449. [%w(v v), 1],
  450. [%w(v t), 1],
  451. [%w(lvt t), 1],
  452. [%w(t t), 1],
  453. [%w(n extend), 1],
  454. [%w(n n), 2],
  455. [%w(n cr lf n), 3],
  456. [%w(n l v t), 2]
  457. ].each do |input, expected_length|
  458. if input.kind_of?(Array)
  459. str = string_from_classes(input)
  460. else
  461. str = input
  462. end
  463. assert_equal expected_length, chars(str).g_length
  464. end
  465. end
  466. def test_tidy_bytes_should_tidy_bytes
  467. byte_string = "\270\236\010\210\245"
  468. tidy_string = [0xb8, 0x17e, 0x8, 0x2c6, 0xa5].pack('U*')
  469. ascii_padding = 'aa'
  470. utf8_padding = 'éé'
  471. assert_equal_codepoints tidy_string, chars(byte_string).tidy_bytes
  472. assert_equal_codepoints ascii_padding.dup.insert(1, tidy_string),
  473. chars(ascii_padding.dup.insert(1, byte_string)).tidy_bytes
  474. assert_equal_codepoints utf8_padding.dup.insert(2, tidy_string),
  475. chars(utf8_padding.dup.insert(2, byte_string)).tidy_bytes
  476. assert_nothing_raised { chars(byte_string).tidy_bytes.to_s.unpack('U*') }
  477. assert_equal_codepoints "\xC3\xA7", chars("\xE7").tidy_bytes # iso_8859_1: small c cedilla
  478. assert_equal_codepoints "\xE2\x80\x9C", chars("\x93").tidy_bytes # win_1252: left smart quote
  479. assert_equal_codepoints "\xE2\x82\xAC", chars("\x80").tidy_bytes # win_1252: euro
  480. assert_equal_codepoints "\x00", chars("\x00").tidy_bytes # null char
  481. assert_equal_codepoints [0xfffd].pack('U'), chars("\xef\xbf\xbd").tidy_bytes # invalid char
  482. rescue ArgumentError => e
  483. raise e if RUBY_VERSION < '1.9'
  484. end
  485. private
  486. def string_from_classes(classes)
  487. # Characters from the character classes as described in UAX #29
  488. character_from_class = {
  489. :l => 0x1100, :v => 0x1160, :t => 0x11A8, :lv => 0xAC00, :lvt => 0xAC01, :cr => 0x000D, :lf => 0x000A,
  490. :extend => 0x094D, :n => 0x64
  491. }
  492. classes.collect do |k|
  493. character_from_class[k.intern]
  494. end.pack('U*')
  495. end
  496. end