did_you_mean 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +48 -0
  3. data/CHANGELOG.md +75 -75
  4. data/Gemfile +2 -1
  5. data/README.md +2 -32
  6. data/Rakefile +4 -5
  7. data/appveyor.yml +25 -0
  8. data/did_you_mean.gemspec +6 -4
  9. data/documentation/CHANGELOG.md.erb +8 -0
  10. data/documentation/changelog_generator.rb +34 -0
  11. data/documentation/human_typo_api.md +20 -0
  12. data/documentation/tree_spell_algorithm.md +82 -0
  13. data/documentation/tree_spell_checker_api.md +24 -0
  14. data/lib/did_you_mean.rb +17 -16
  15. data/lib/did_you_mean/experimental.rb +2 -2
  16. data/lib/did_you_mean/experimental/initializer_name_correction.rb +1 -1
  17. data/lib/did_you_mean/experimental/ivar_name_correction.rb +3 -1
  18. data/lib/did_you_mean/levenshtein.rb +1 -1
  19. data/lib/did_you_mean/spell_checker.rb +7 -7
  20. data/lib/did_you_mean/spell_checkers/key_error_checker.rb +8 -2
  21. data/lib/did_you_mean/spell_checkers/method_name_checker.rb +14 -6
  22. data/lib/did_you_mean/spell_checkers/name_error_checkers.rb +2 -2
  23. data/lib/did_you_mean/spell_checkers/name_error_checkers/class_name_checker.rb +5 -5
  24. data/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb +1 -1
  25. data/lib/did_you_mean/tree_spell_checker.rb +137 -0
  26. data/lib/did_you_mean/verbose.rb +2 -2
  27. data/lib/did_you_mean/version.rb +1 -1
  28. data/test/core_ext/test_name_error_extension.rb +48 -0
  29. data/test/edit_distance/{jaro_winkler_test.rb → test_jaro_winkler.rb} +2 -2
  30. data/test/fixtures/mini_dir.yml +15 -0
  31. data/test/fixtures/rspec_dir.yml +112 -0
  32. data/test/helper.rb +29 -0
  33. data/test/spell_checking/{class_name_check_test.rb → test_class_name_check.rb} +12 -10
  34. data/test/spell_checking/{key_name_check_test.rb → test_key_name_check.rb} +18 -8
  35. data/test/spell_checking/{method_name_check_test.rb → test_method_name_check.rb} +17 -15
  36. data/test/spell_checking/{uncorrectable_name_check_test.rb → test_uncorrectable_name_check.rb} +3 -3
  37. data/test/spell_checking/{variable_name_check_test.rb → test_variable_name_check.rb} +18 -16
  38. data/test/{spell_checker_test.rb → test_spell_checker.rb} +2 -2
  39. data/test/test_tree_spell_checker.rb +173 -0
  40. data/test/test_verbose_formatter.rb +21 -0
  41. data/test/tree_spell/change_word.rb +61 -0
  42. data/test/tree_spell/human_typo.rb +89 -0
  43. data/test/tree_spell/test_change_word.rb +38 -0
  44. data/test/tree_spell/test_explore.rb +128 -0
  45. data/test/tree_spell/test_human_typo.rb +24 -0
  46. metadata +47 -58
  47. data/.travis.yml +0 -23
  48. data/test/core_ext/name_error_extension_test.rb +0 -51
  49. data/test/experimental/initializer_name_correction_test.rb +0 -15
  50. data/test/experimental/method_name_checker_test.rb +0 -13
  51. data/test/test_helper.rb +0 -13
  52. data/test/verbose_formatter_test.rb +0 -22
@@ -0,0 +1,21 @@
1
+ require_relative './helper'
2
+
3
+ class VerboseFormatterTest < Test::Unit::TestCase
4
+ def setup
5
+ require_relative File.join(DidYouMean::TestHelper.root, 'verbose')
6
+ end
7
+
8
+ def teardown
9
+ DidYouMean.formatter = DidYouMean::PlainFormatter.new
10
+ end
11
+
12
+ def test_message
13
+ @error = assert_raise(NoMethodError){ 1.zeor? }
14
+
15
+ assert_match <<~MESSAGE.strip, @error.message
16
+ undefined method `zeor?' for 1:Integer
17
+
18
+ Did you mean? zero?
19
+ MESSAGE
20
+ end
21
+ end
@@ -0,0 +1,61 @@
1
+ module TreeSpell
2
+ # Changes a word with one of four actions:
3
+ # insertion, substitution, deletion and transposition.
4
+ class ChangeWord
5
+ # initialize with input string
6
+ def initialize(input)
7
+ @input = input
8
+ @len = input.length
9
+ end
10
+
11
+ # insert char after index of i_place
12
+ def insertion(i_place, char)
13
+ @word = input.dup
14
+ return char + word if i_place == 0
15
+ return word + char if i_place == len - 1
16
+ word.insert(i_place + 1, char)
17
+ end
18
+
19
+ # substitute char at index of i_place
20
+ def substitution(i_place, char)
21
+ @word = input.dup
22
+ word[i_place] = char
23
+ word
24
+ end
25
+
26
+ # delete character at index of i_place
27
+ def deletion(i_place)
28
+ @word = input.dup
29
+ word.slice!(i_place)
30
+ word
31
+ end
32
+
33
+ # transpose char at i_place with char at i_place + direction
34
+ # if i_place + direction is out of bounds just swap in other direction
35
+ def transposition(i_place, direction)
36
+ @word = input.dup
37
+ w = word.dup
38
+ return swap_first_two(w) if i_place + direction < 0
39
+ return swap_last_two(w) if i_place + direction >= len
40
+ swap_two(w, i_place, direction)
41
+ w
42
+ end
43
+
44
+ private
45
+
46
+ attr_accessor :word, :input, :len
47
+
48
+ def swap_first_two(w)
49
+ w[1] + w[0] + word[2..-1]
50
+ end
51
+
52
+ def swap_last_two(w)
53
+ w[0...(len - 2)] + word[len - 1] + word[len - 2]
54
+ end
55
+
56
+ def swap_two(w, i_place, direction)
57
+ w[i_place] = word[i_place + direction]
58
+ w[i_place + direction] = word[i_place]
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,89 @@
1
+ # module for classes needed to test TreeSpellChecker
2
+ module TreeSpell
3
+ require_relative 'change_word'
4
+ # Simulate an error prone human typist
5
+ # see doc/human_typo_api.md for the api description
6
+ class HumanTypo
7
+ def initialize(input, lambda: 0.05)
8
+ @input = input
9
+ check_input
10
+ @len = input.length
11
+ @lambda = lambda
12
+ end
13
+
14
+ def call
15
+ @word = input.dup
16
+ i_place = initialize_i_place
17
+ loop do
18
+ action = action_type
19
+ @word = make_change action, i_place
20
+ @len = word.length
21
+ i_place += exponential
22
+ break if i_place >= len
23
+ end
24
+ word
25
+ end
26
+
27
+ private
28
+
29
+ attr_accessor :input, :word, :len, :lambda
30
+
31
+ def initialize_i_place
32
+ i_place = nil
33
+ loop do
34
+ i_place = exponential
35
+ break if i_place < len
36
+ end
37
+ i_place
38
+ end
39
+
40
+ def exponential
41
+ (rand / (lambda / 2)).to_i
42
+ end
43
+
44
+ def rand_char
45
+ popular_chars = alphabetic_characters + special_characters
46
+ n = popular_chars.length
47
+ popular_chars[rand(n)]
48
+ end
49
+
50
+ def alphabetic_characters
51
+ ('a'..'z').to_a.join + ('A'..'Z').to_a.join
52
+ end
53
+
54
+ def special_characters
55
+ '?<>,.!`+=-_":;@#$%^&*()'
56
+ end
57
+
58
+ def toss
59
+ return +1 if rand >= 0.5
60
+ -1
61
+ end
62
+
63
+ def action_type
64
+ [:insert, :transpose, :delete, :substitute][rand(4)]
65
+ end
66
+
67
+ def make_change(action, i_place)
68
+ cw = ChangeWord.new(word)
69
+ case action
70
+ when :delete
71
+ cw.deletion(i_place)
72
+ when :insert
73
+ cw.insertion(i_place, rand_char)
74
+ when :substitute
75
+ cw.substitution(i_place, rand_char)
76
+ when :transpose
77
+ cw.transposition(i_place, toss)
78
+ end
79
+ end
80
+
81
+ def check_input
82
+ fail check_input_message if input.nil? || input.length < 5
83
+ end
84
+
85
+ def check_input_message
86
+ "input length must be greater than 5 characters: #{input}"
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,38 @@
1
+ require_relative '../helper'
2
+ require_relative 'change_word'
3
+
4
+ class ChangeWordTest < Test::Unit::TestCase
5
+ def setup
6
+ @input = 'spec/services/anything_spec'
7
+ @cw = TreeSpell::ChangeWord.new(@input)
8
+ @len = @input.length
9
+ end
10
+
11
+ def test_deleletion
12
+ assert_match @cw.deletion(5), 'spec/ervices/anything_spec'
13
+ assert_match @cw.deletion(@len - 1), 'spec/services/anything_spe'
14
+ assert_match @cw.deletion(0), 'pec/services/anything_spec'
15
+ end
16
+
17
+ def test_substitution
18
+ assert_match @cw.substitution(5, '$'), 'spec/$ervices/anything_spec'
19
+ assert_match @cw.substitution(@len - 1, '$'), 'spec/services/anything_spe$'
20
+ assert_match @cw.substitution(0, '$'), '$pec/services/anything_spec'
21
+ end
22
+
23
+ def test_insertion
24
+ assert_match @cw.insertion(7, 'X'), 'spec/serXvices/anything_spec'
25
+ assert_match @cw.insertion(0, 'X'), 'Xspec/services/anything_spec'
26
+ assert_match @cw.insertion(@len - 1, 'X'), 'spec/services/anything_specX'
27
+ end
28
+
29
+ def test_transposition
30
+ n = @input.length
31
+ assert_match @cw.transposition(0, -1), 'psec/services/anything_spec'
32
+ assert_match @cw.transposition(n - 1, +1), 'spec/services/anything_spce'
33
+ assert_match @cw.transposition(4, +1), 'specs/ervices/anything_spec'
34
+ assert_match @cw.transposition(4, -1), 'spe/cservices/anything_spec'
35
+ assert_match @cw.transposition(21, -1), 'spec/services/anythign_spec'
36
+ assert_match @cw.transposition(21, +1), 'spec/services/anythin_gspec'
37
+ end
38
+ end
@@ -0,0 +1,128 @@
1
+ require 'set'
2
+ require 'yaml'
3
+
4
+ require_relative '../helper'
5
+ require_relative 'human_typo'
6
+
7
+ # statistical tests on tree_spell algorithms
8
+ class ExploreTest < Test::Unit::TestCase
9
+ MINI_DIRECTORIES = YAML.load_file(File.expand_path('../fixtures/mini_dir.yml', __dir__))
10
+ RSPEC_DIRECTORIES = YAML.load_file(File.expand_path('../fixtures/rspec_dir.yml', __dir__))
11
+
12
+ def test_checkers_with_many_typos_on_mini
13
+ n_repeat = 10_000
14
+ many_typos n_repeat, MINI_DIRECTORIES, 'Minitest'
15
+ end
16
+
17
+ def test_checkers_with_many_typos_on_rspec
18
+ n_repeat = 10_000
19
+ many_typos n_repeat, RSPEC_DIRECTORIES, 'Rspec'
20
+ end
21
+
22
+ def test_human_typo
23
+ n_repeat = 10_000
24
+ total_changes = 0
25
+ word = 'any_string_that_is_40_characters_long_sp'
26
+ n_repeat.times do
27
+ word_error = TreeSpell::HumanTypo.new(word).call
28
+ total_changes += DidYouMean::Levenshtein.distance(word, word_error)
29
+ end
30
+ mean_changes = (total_changes.to_f / n_repeat).round(2)
31
+ puts ''
32
+ puts "HumanTypo mean_changes: #{mean_changes} with n_repeat: #{n_repeat}"
33
+ puts 'Expected mean_changes: 2.1 with n_repeat: 10000, plus/minus 0.03'
34
+ puts ''
35
+ end
36
+
37
+ def test_execution_speed
38
+ n_repeat = 1_000
39
+ puts ''
40
+ puts 'Testing execution time of Standard'
41
+ measure_execution_speed(n_repeat) do |files, error|
42
+ DidYouMean::SpellChecker.new(dictionary: files).correct error
43
+ end
44
+ puts ''
45
+ puts 'Testing execution time of Tree'
46
+ measure_execution_speed(n_repeat) do |files, error|
47
+ DidYouMean::TreeSpellChecker.new(dictionary: files).correct error
48
+ end
49
+ puts ''
50
+ puts 'Testing execution time of Augmented Tree'
51
+ measure_execution_speed(n_repeat) do |files, error|
52
+ DidYouMean::TreeSpellChecker.new(dictionary: files, augment: true).correct error
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def measure_execution_speed(n_repeat, &block)
59
+ len = RSPEC_DIRECTORIES.length
60
+ start_time = Time.now
61
+ n_repeat.times do
62
+ word = RSPEC_DIRECTORIES[rand len]
63
+ word_error = TreeSpell::HumanTypo.new(word).call
64
+ block.call(RSPEC_DIRECTORIES, word_error)
65
+ end
66
+ time_ms = (Time.now - start_time).to_f * 1000 / n_repeat
67
+ puts "Average time (ms): #{time_ms.round(1)}"
68
+ end
69
+
70
+ def many_typos(n_repeat, files, title)
71
+ first_times = [0, 0, 0]
72
+ total_suggestions = [0, 0, 0]
73
+ total_failures = [0, 0, 0]
74
+ len = files.length
75
+ n_repeat.times do
76
+ word = files[rand len]
77
+ word_error = TreeSpell::HumanTypo.new(word).call
78
+ suggestions_a = group_suggestions word_error, files
79
+ check_first_is_right word, suggestions_a, first_times
80
+ check_no_suggestions suggestions_a, total_suggestions
81
+ check_for_failure word, suggestions_a, total_failures
82
+ end
83
+ print_results first_times, total_suggestions, total_failures, n_repeat, title
84
+ end
85
+
86
+ def group_suggestions(word_error, files)
87
+ a0 = DidYouMean::TreeSpellChecker.new(dictionary: files).correct word_error
88
+ a1 = ::DidYouMean::SpellChecker.new(dictionary: files).correct word_error
89
+ a2 = a0.empty? ? a1 : a0
90
+ [a0, a1, a2]
91
+ end
92
+
93
+ def check_for_failure(word, suggestions_a, total_failures)
94
+ suggestions_a.each_with_index.map do |a, i|
95
+ total_failures[i] += 1 unless a.include? word
96
+ end
97
+ end
98
+
99
+ def check_first_is_right(word, suggestions_a, first_times)
100
+ suggestions_a.each_with_index.map do |a, i|
101
+ first_times[i] += 1 if word == a.first
102
+ end
103
+ end
104
+
105
+ def check_no_suggestions(suggestions_a, total_suggestions)
106
+ suggestions_a.each_with_index.map do |a, i|
107
+ total_suggestions[i] += a.length
108
+ end
109
+ end
110
+
111
+ def print_results(first_times, total_suggestions, total_failures, n_repeat, title)
112
+ algorithms = ['Tree ', 'Standard ', 'Augmented']
113
+ print_header title
114
+ (0..2).each do |i|
115
+ ft = (first_times[i].to_f / n_repeat * 100).round(1)
116
+ mns = (total_suggestions[i].to_f / (n_repeat - total_failures[i])).round(1)
117
+ f = (total_failures[i].to_f / n_repeat * 100).round(1)
118
+ puts " #{algorithms[i]} #{' ' * 7} #{ft} #{' ' * 14} #{mns} #{' ' * 15} #{f} #{' ' * 16}"
119
+ end
120
+ end
121
+
122
+ def print_header(title)
123
+ puts "#{' ' * 30} #{title} Summary #{' ' * 31}"
124
+ puts '-' * 80
125
+ puts " Method | First Time (\%) Mean Suggestions Failures (\%) #{' ' * 13}"
126
+ puts '-' * 80
127
+ end
128
+ end
@@ -0,0 +1,24 @@
1
+ require_relative '../helper'
2
+ require_relative 'human_typo'
3
+
4
+ class HumanTypoTest < Test::Unit::TestCase
5
+ def setup
6
+ @input = 'spec/services/anything_spec'
7
+ @sh = TreeSpell::HumanTypo.new(@input, lambda: 0.05)
8
+ @len = @input.length
9
+ end
10
+
11
+ def test_changes
12
+ # srand seed ensures all four actions are called
13
+ srand 247_696_449
14
+ sh = TreeSpell::HumanTypo.new(@input, lambda: 0.20)
15
+ word_error = sh.call
16
+ assert_equal word_error, 'spec/suervcieq/anythin_gpec'
17
+ end
18
+
19
+ def test_check_input
20
+ assert_raise(RuntimeError, "input length must be greater than 5 characters: tiny") do
21
+ TreeSpell::HumanTypo.new('tiny')
22
+ end
23
+ end
24
+ end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: did_you_mean
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuki Nishijima
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-29 00:00:00.000000000 Z
11
+ date: 2019-12-24 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: rake
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -38,20 +24,6 @@ dependencies:
38
24
  - - ">="
39
25
  - !ruby/object:Gem::Version
40
26
  version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: minitest
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
27
  description: The gem that has been saving people from typos since 2014.
56
28
  email:
57
29
  - mail@yukinishijima.net
@@ -59,13 +31,14 @@ executables: []
59
31
  extensions: []
60
32
  extra_rdoc_files: []
61
33
  files:
34
+ - ".github/workflows/ruby.yml"
62
35
  - ".gitignore"
63
- - ".travis.yml"
64
36
  - CHANGELOG.md
65
37
  - Gemfile
66
38
  - LICENSE.txt
67
39
  - README.md
68
40
  - Rakefile
41
+ - appveyor.yml
69
42
  - benchmark/jaro_winkler/memory_usage.rb
70
43
  - benchmark/jaro_winkler/speed.rb
71
44
  - benchmark/levenshtein/memory_usage.rb
@@ -73,8 +46,11 @@ files:
73
46
  - benchmark/memory_usage.rb
74
47
  - benchmark/speed.yml
75
48
  - did_you_mean.gemspec
76
- - doc/CHANGELOG.md.erb
77
- - doc/changelog_generator.rb
49
+ - documentation/CHANGELOG.md.erb
50
+ - documentation/changelog_generator.rb
51
+ - documentation/human_typo_api.md
52
+ - documentation/tree_spell_algorithm.md
53
+ - documentation/tree_spell_checker_api.md
78
54
  - lib/did_you_mean.rb
79
55
  - lib/did_you_mean/core_ext/name_error.rb
80
56
  - lib/did_you_mean/experimental.rb
@@ -91,22 +67,29 @@ files:
91
67
  - lib/did_you_mean/spell_checkers/name_error_checkers/class_name_checker.rb
92
68
  - lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb
93
69
  - lib/did_you_mean/spell_checkers/null_checker.rb
70
+ - lib/did_you_mean/tree_spell_checker.rb
94
71
  - lib/did_you_mean/verbose.rb
95
72
  - lib/did_you_mean/version.rb
96
- - test/core_ext/name_error_extension_test.rb
97
- - test/edit_distance/jaro_winkler_test.rb
98
- - test/experimental/initializer_name_correction_test.rb
99
- - test/experimental/method_name_checker_test.rb
73
+ - test/core_ext/test_name_error_extension.rb
74
+ - test/edit_distance/test_jaro_winkler.rb
100
75
  - test/fixtures/book.rb
101
- - test/spell_checker_test.rb
102
- - test/spell_checking/class_name_check_test.rb
103
- - test/spell_checking/key_name_check_test.rb
104
- - test/spell_checking/method_name_check_test.rb
105
- - test/spell_checking/uncorrectable_name_check_test.rb
106
- - test/spell_checking/variable_name_check_test.rb
107
- - test/test_helper.rb
108
- - test/verbose_formatter_test.rb
109
- homepage: https://github.com/yuki24/did_you_mean
76
+ - test/fixtures/mini_dir.yml
77
+ - test/fixtures/rspec_dir.yml
78
+ - test/helper.rb
79
+ - test/spell_checking/test_class_name_check.rb
80
+ - test/spell_checking/test_key_name_check.rb
81
+ - test/spell_checking/test_method_name_check.rb
82
+ - test/spell_checking/test_uncorrectable_name_check.rb
83
+ - test/spell_checking/test_variable_name_check.rb
84
+ - test/test_spell_checker.rb
85
+ - test/test_tree_spell_checker.rb
86
+ - test/test_verbose_formatter.rb
87
+ - test/tree_spell/change_word.rb
88
+ - test/tree_spell/human_typo.rb
89
+ - test/tree_spell/test_change_word.rb
90
+ - test/tree_spell/test_explore.rb
91
+ - test/tree_spell/test_human_typo.rb
92
+ homepage: https://github.com/ruby/did_you_mean
110
93
  licenses:
111
94
  - MIT
112
95
  metadata: {}
@@ -130,16 +113,22 @@ signing_key:
130
113
  specification_version: 4
131
114
  summary: '"Did you mean?" experience in Ruby'
132
115
  test_files:
133
- - test/core_ext/name_error_extension_test.rb
134
- - test/edit_distance/jaro_winkler_test.rb
135
- - test/experimental/initializer_name_correction_test.rb
136
- - test/experimental/method_name_checker_test.rb
116
+ - test/core_ext/test_name_error_extension.rb
117
+ - test/edit_distance/test_jaro_winkler.rb
137
118
  - test/fixtures/book.rb
138
- - test/spell_checker_test.rb
139
- - test/spell_checking/class_name_check_test.rb
140
- - test/spell_checking/key_name_check_test.rb
141
- - test/spell_checking/method_name_check_test.rb
142
- - test/spell_checking/uncorrectable_name_check_test.rb
143
- - test/spell_checking/variable_name_check_test.rb
144
- - test/test_helper.rb
145
- - test/verbose_formatter_test.rb
119
+ - test/fixtures/mini_dir.yml
120
+ - test/fixtures/rspec_dir.yml
121
+ - test/helper.rb
122
+ - test/spell_checking/test_class_name_check.rb
123
+ - test/spell_checking/test_key_name_check.rb
124
+ - test/spell_checking/test_method_name_check.rb
125
+ - test/spell_checking/test_uncorrectable_name_check.rb
126
+ - test/spell_checking/test_variable_name_check.rb
127
+ - test/test_spell_checker.rb
128
+ - test/test_tree_spell_checker.rb
129
+ - test/test_verbose_formatter.rb
130
+ - test/tree_spell/change_word.rb
131
+ - test/tree_spell/human_typo.rb
132
+ - test/tree_spell/test_change_word.rb
133
+ - test/tree_spell/test_explore.rb
134
+ - test/tree_spell/test_human_typo.rb